/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.swing;

import java.util.*;

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.awt.print.*;

import java.beans.*;

import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;

import javax.accessibility.*;

import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.table.*;
import javax.swing.border.*;

import java.text.NumberFormat;
import java.text.DateFormat;
import java.text.MessageFormat;

import javax.print.attribute.*;
import javax.print.PrintService;
import sun.reflect.misc.ReflectUtil;

import sun.swing.SwingUtilities2;
import sun.swing.SwingUtilities2.Section;

import static sun.swing.SwingUtilities2.Section.*;

import sun.swing.PrintingStatus;

/**
 * The <code>JTable</code> is used to display and edit regular two-dimensional tables of cells. See
 * <a href="https://docs.oracle.com/javase/tutorial/uiswing/components/table.html">How to Use
 * Tables</a> in <em>The Java Tutorial</em> for task-oriented documentation and examples of using
 * <code>JTable</code>.
 *
 * <p> The <code>JTable</code> has many facilities that make it possible to customize its rendering
 * and editing but provides defaults for these features so that simple tables can be set up easily.
 * For example, to set up a table with 10 rows and 10 columns of numbers:
 *
 * <pre>
 *      TableModel dataModel = new AbstractTableModel() {
 *          public int getColumnCount() { return 10; }
 *          public int getRowCount() { return 10;}
 *          public Object getValueAt(int row, int col) { return new Integer(row*col); }
 *      };
 *      JTable table = new JTable(dataModel);
 *      JScrollPane scrollpane = new JScrollPane(table);
 * </pre>
 * <p> {@code JTable}s are typically placed inside of a {@code JScrollPane}.  By default, a {@code
 * JTable} will adjust its width such that a horizontal scrollbar is unnecessary.  To allow for a
 * horizontal scrollbar, invoke {@link #setAutoResizeMode} with {@code AUTO_RESIZE_OFF}. Note that
 * if you wish to use a <code>JTable</code> in a standalone view (outside of a
 * <code>JScrollPane</code>) and want the header displayed, you can get it using {@link
 * #getTableHeader} and display it separately. <p> To enable sorting and filtering of rows, use a
 * {@code RowSorter}. You can set up a row sorter in either of two ways: <ul> <li>Directly set the
 * {@code RowSorter}. For example: {@code table.setRowSorter(new TableRowSorter(model))}. <li>Set
 * the {@code autoCreateRowSorter} property to {@code true}, so that the {@code JTable} creates a
 * {@code RowSorter} for you. For example: {@code setAutoCreateRowSorter(true)}. </ul> <p> When
 * designing applications that use the <code>JTable</code> it is worth paying close attention to the
 * data structures that will represent the table's data. The <code>DefaultTableModel</code> is a
 * model implementation that uses a <code>Vector</code> of <code>Vector</code>s of
 * <code>Object</code>s to store the cell values. As well as copying the data from an application
 * into the <code>DefaultTableModel</code>, it is also possible to wrap the data in the methods of
 * the <code>TableModel</code> interface so that the data can be passed to the <code>JTable</code>
 * directly, as in the example above. This often results in more efficient applications because the
 * model is free to choose the internal representation that best suits the data. A good rule of
 * thumb for deciding whether to use the <code>AbstractTableModel</code> or the
 * <code>DefaultTableModel</code> is to use the <code>AbstractTableModel</code> as the base class
 * for creating subclasses and the <code>DefaultTableModel</code> when subclassing is not required.
 * <p> The "TableExample" directory in the demo area of the source distribution gives a number of
 * complete examples of <code>JTable</code> usage, covering how the <code>JTable</code> can be used
 * to provide an editable view of data taken from a database and how to modify the columns in the
 * display to use specialized renderers and editors. <p> The <code>JTable</code> uses integers
 * exclusively to refer to both the rows and the columns of the model that it displays. The
 * <code>JTable</code> simply takes a tabular range of cells and uses <code>getValueAt(int,
 * int)</code> to retrieve the values from the model during painting.  It is important to remember
 * that the column and row indexes returned by various <code>JTable</code> methods are in terms of
 * the <code>JTable</code> (the view) and are not necessarily the same indexes used by the model.
 * <p> By default, columns may be rearranged in the <code>JTable</code> so that the view's columns
 * appear in a different order to the columns in the model. This does not affect the implementation
 * of the model at all: when the columns are reordered, the <code>JTable</code> maintains the new
 * order of the columns internally and converts its column indices before querying the model. <p>
 * So, when writing a <code>TableModel</code>, it is not necessary to listen for column reordering
 * events as the model will be queried in its own coordinate system regardless of what is happening
 * in the view. In the examples area there is a demonstration of a sorting algorithm making use of
 * exactly this technique to interpose yet another coordinate system where the order of the rows is
 * changed, rather than the order of the columns. <p> Similarly when using the sorting and filtering
 * functionality provided by <code>RowSorter</code> the underlying <code>TableModel</code> does not
 * need to know how to do sorting, rather <code>RowSorter</code> will handle it.  Coordinate
 * conversions will be necessary when using the row based methods of <code>JTable</code> with the
 * underlying <code>TableModel</code>. All of <code>JTable</code>s row based methods are in terms of
 * the <code>RowSorter</code>, which is not necessarily the same as that of the underlying
 * <code>TableModel</code>.  For example, the selection is always in terms of <code>JTable</code> so
 * that when using <code>RowSorter</code> you will need to convert using
 * <code>convertRowIndexToView</code> or <code>convertRowIndexToModel</code>.  The following shows
 * how to convert coordinates from <code>JTable</code> to that of the underlying model:
 * <pre>
 *   int[] selection = table.getSelectedRows();
 *   for (int i = 0; i &lt; selection.length; i++) {
 *     selection[i] = table.convertRowIndexToModel(selection[i]);
 *   }
 *   // selection is now in terms of the underlying TableModel
 * </pre>
 * <p> By default if sorting is enabled <code>JTable</code> will persist the selection and variable
 * row heights in terms of the model on sorting.  For example if row 0, in terms of the underlying
 * model, is currently selected, after the sort row 0, in terms of the underlying model will be
 * selected.  Visually the selection may change, but in terms of the underlying model it will remain
 * the same.  The one exception to that is if the model index is no longer visible or was removed.
 * For example, if row 0 in terms of model was filtered out the selection will be empty after the
 * sort. <p> J2SE 5 adds methods to <code>JTable</code> to provide convenient access to some common
 * printing needs. Simple new {@link #print()} methods allow for quick and easy addition of printing
 * support to your application. In addition, a new {@link #getPrintable} method is available for
 * more advanced printing needs. <p> As for all <code>JComponent</code> classes, you can use {@link
 * InputMap} and {@link ActionMap} to associate an {@link Action} object with a {@link KeyStroke}
 * and execute the action under specified conditions. <p> <strong>Warning:</strong> Swing is not
 * thread safe. For more information see <a href="package-summary.html#threading">Swing's Threading
 * Policy</a>. <p> <strong>Warning:</strong> Serialized objects of this class will not be compatible
 * with future Swing releases. The current serialization support is appropriate for short term
 * storage or RMI between applications running the same version of Swing.  As of 1.4, support for
 * long term storage of all JavaBeans&trade; has been added to the <code>java.beans</code> package.
 * Please see {@link java.beans.XMLEncoder}.
 *
 * @author Philip Milne
 * @author Shannon Hickey (printing support)
 * @beaninfo attribute: isContainer false description: A component which displays data in a two
 * dimensional grid.
 * @see javax.swing.table.DefaultTableModel
 * @see javax.swing.table.TableRowSorter
 */
/* The first versions of the JTable, contained in Swing-0.1 through
 * Swing-0.4, were written by Alan Chung.
 */
public class JTable extends JComponent implements TableModelListener, Scrollable,
    TableColumnModelListener, ListSelectionListener, CellEditorListener,
    Accessible, RowSorterListener {
//
// Static Constants
//

  /**
   * @see #getUIClassID
   * @see #readObject
   */
  private static final String uiClassID = "TableUI";

  /**
   * Do not adjust column widths automatically; use a horizontal scrollbar instead.
   */
  public static final int AUTO_RESIZE_OFF = 0;

  /**
   * When a column is adjusted in the UI, adjust the next column the opposite way.
   */
  public static final int AUTO_RESIZE_NEXT_COLUMN = 1;

  /**
   * During UI adjustment, change subsequent columns to preserve the total width;
   * this is the default behavior.
   */
  public static final int AUTO_RESIZE_SUBSEQUENT_COLUMNS = 2;

  /**
   * During all resize operations, apply adjustments to the last column only.
   */
  public static final int AUTO_RESIZE_LAST_COLUMN = 3;

  /**
   * During all resize operations, proportionately resize all columns.
   */
  public static final int AUTO_RESIZE_ALL_COLUMNS = 4;


  /**
   * Printing modes, used in printing <code>JTable</code>s.
   *
   * @see #print(JTable.PrintMode, MessageFormat, MessageFormat, boolean, PrintRequestAttributeSet,
   * boolean)
   * @see #getPrintable
   * @since 1.5
   */
  public enum PrintMode {

    /**
     * Printing mode that prints the table at its current size,
     * spreading both columns and rows across multiple pages if necessary.
     */
    NORMAL,

    /**
     * Printing mode that scales the output smaller, if necessary,
     * to fit the table's entire width (and thereby all columns) on each page;
     * Rows are spread across multiple pages as necessary.
     */
    FIT_WIDTH
  }

//
// Instance Variables
//

  /**
   * The <code>TableModel</code> of the table.
   */
  protected TableModel dataModel;

  /**
   * The <code>TableColumnModel</code> of the table.
   */
  protected TableColumnModel columnModel;

  /**
   * The <code>ListSelectionModel</code> of the table, used to keep track of row selections.
   */
  protected ListSelectionModel selectionModel;

  /**
   * The <code>TableHeader</code> working with the table.
   */
  protected JTableHeader tableHeader;

  /**
   * The height in pixels of each row in the table.
   */
  protected int rowHeight;

  /**
   * The height in pixels of the margin between the cells in each row.
   */
  protected int rowMargin;

  /**
   * The color of the grid.
   */
  protected Color gridColor;

  /**
   * The table draws horizontal lines between cells if <code>showHorizontalLines</code> is true.
   */
  protected boolean showHorizontalLines;

  /**
   * The table draws vertical lines between cells if <code>showVerticalLines</code> is true.
   */
  protected boolean showVerticalLines;

  /**
   * Determines if the table automatically resizes the
   * width of the table's columns to take up the entire width of the
   * table, and how it does the resizing.
   */
  protected int autoResizeMode;

  /**
   * The table will query the <code>TableModel</code> to build the default
   * set of columns if this is true.
   */
  protected boolean autoCreateColumnsFromModel;

  /**
   * Used by the <code>Scrollable</code> interface to determine the initial visible area.
   */
  protected Dimension preferredViewportSize;

  /**
   * True if row selection is allowed in this table.
   */
  protected boolean rowSelectionAllowed;

  /**
   * Obsolete as of Java 2 platform v1.3.  Please use the
   * <code>rowSelectionAllowed</code> property and the
   * <code>columnSelectionAllowed</code> property of the
   * <code>columnModel</code> instead. Or use the
   * method <code>getCellSelectionEnabled</code>.
   */
    /*
     * If true, both a row selection and a column selection
     * can be non-empty at the same time, the selected cells are the
     * the cells whose row and column are both selected.
     */
  protected boolean cellSelectionEnabled;

  /**
   * If editing, the <code>Component</code> that is handling the editing.
   */
  transient protected Component editorComp;

  /**
   * The active cell editor object, that overwrites the screen real estate
   * occupied by the current cell and allows the user to change its contents.
   * {@code null} if the table isn't currently editing.
   */
  transient protected TableCellEditor cellEditor;

  /**
   * Identifies the column of the cell being edited.
   */
  transient protected int editingColumn;

  /**
   * Identifies the row of the cell being edited.
   */
  transient protected int editingRow;

  /**
   * A table of objects that display the contents of a cell,
   * indexed by class as declared in <code>getColumnClass</code>
   * in the <code>TableModel</code> interface.
   */
  transient protected Hashtable defaultRenderersByColumnClass;

  /**
   * A table of objects that display and edit the contents of a cell,
   * indexed by class as declared in <code>getColumnClass</code>
   * in the <code>TableModel</code> interface.
   */
  transient protected Hashtable defaultEditorsByColumnClass;

  /**
   * The foreground color of selected cells.
   */
  protected Color selectionForeground;

  /**
   * The background color of selected cells.
   */
  protected Color selectionBackground;

//
// Private state
//

  // WARNING: If you directly access this field you should also change the
  // SortManager.modelRowSizes field as well.
  private SizeSequence rowModel;
  private boolean dragEnabled;
  private boolean surrendersFocusOnKeystroke;
  private PropertyChangeListener editorRemover = null;
  /**
   * The last value of getValueIsAdjusting from the column selection models
   * columnSelectionChanged notification. Used to test if a repaint is
   * needed.
   */
  private boolean columnSelectionAdjusting;
  /**
   * The last value of getValueIsAdjusting from the row selection models
   * valueChanged notification. Used to test if a repaint is needed.
   */
  private boolean rowSelectionAdjusting;

  /**
   * To communicate errors between threads during printing.
   */
  private Throwable printError;

  /**
   * True when setRowHeight(int) has been invoked.
   */
  private boolean isRowHeightSet;

  /**
   * If true, on a sort the selection is reset.
   */
  private boolean updateSelectionOnSort;

  /**
   * Information used in sorting.
   */
  private transient SortManager sortManager;

  /**
   * If true, when sorterChanged is invoked it's value is ignored.
   */
  private boolean ignoreSortChange;

  /**
   * Whether or not sorterChanged has been invoked.
   */
  private boolean sorterChanged;

  /**
   * If true, any time the model changes a new RowSorter is set.
   */
  private boolean autoCreateRowSorter;

  /**
   * Whether or not the table always fills the viewport height.
   *
   * @see #setFillsViewportHeight
   * @see #getScrollableTracksViewportHeight
   */
  private boolean fillsViewportHeight;

  /**
   * The drop mode for this component.
   */
  private DropMode dropMode = DropMode.USE_SELECTION;

  /**
   * The drop location.
   */
  private transient DropLocation dropLocation;

  /**
   * A subclass of <code>TransferHandler.DropLocation</code> representing
   * a drop location for a <code>JTable</code>.
   *
   * @see #getDropLocation
   * @since 1.6
   */
  public static final class DropLocation extends TransferHandler.DropLocation {

    private final int row;
    private final int col;
    private final boolean isInsertRow;
    private final boolean isInsertCol;

    private DropLocation(Point p, int row, int col,
        boolean isInsertRow, boolean isInsertCol) {

      super(p);
      this.row = row;
      this.col = col;
      this.isInsertRow = isInsertRow;
      this.isInsertCol = isInsertCol;
    }

    /**
     * Returns the row index where a dropped item should be placed in the
     * table. Interpretation of the value depends on the return of
     * <code>isInsertRow()</code>. If that method returns
     * <code>true</code> this value indicates the index where a new
     * row should be inserted. Otherwise, it represents the value
     * of an existing row on which the data was dropped. This index is
     * in terms of the view.
     * <p>
     * <code>-1</code> indicates that the drop occurred over empty space,
     * and no row could be calculated.
     *
     * @return the drop row
     */
    public int getRow() {
      return row;
    }

    /**
     * Returns the column index where a dropped item should be placed in the
     * table. Interpretation of the value depends on the return of
     * <code>isInsertColumn()</code>. If that method returns
     * <code>true</code> this value indicates the index where a new
     * column should be inserted. Otherwise, it represents the value
     * of an existing column on which the data was dropped. This index is
     * in terms of the view.
     * <p>
     * <code>-1</code> indicates that the drop occurred over empty space,
     * and no column could be calculated.
     *
     * @return the drop row
     */
    public int getColumn() {
      return col;
    }

    /**
     * Returns whether or not this location represents an insert
     * of a row.
     *
     * @return whether or not this is an insert row
     */
    public boolean isInsertRow() {
      return isInsertRow;
    }

    /**
     * Returns whether or not this location represents an insert
     * of a column.
     *
     * @return whether or not this is an insert column
     */
    public boolean isInsertColumn() {
      return isInsertCol;
    }

    /**
     * Returns a string representation of this drop location.
     * This method is intended to be used for debugging purposes,
     * and the content and format of the returned string may vary
     * between implementations.
     *
     * @return a string representation of this drop location
     */
    public String toString() {
      return getClass().getName()
          + "[dropPoint=" + getDropPoint() + ","
          + "row=" + row + ","
          + "column=" + col + ","
          + "insertRow=" + isInsertRow + ","
          + "insertColumn=" + isInsertCol + "]";
    }
  }

//
// Constructors
//

  /**
   * Constructs a default <code>JTable</code> that is initialized with a default
   * data model, a default column model, and a default selection
   * model.
   *
   * @see #createDefaultDataModel
   * @see #createDefaultColumnModel
   * @see #createDefaultSelectionModel
   */
  public JTable() {
    this(null, null, null);
  }

  /**
   * Constructs a <code>JTable</code> that is initialized with
   * <code>dm</code> as the data model, a default column model,
   * and a default selection model.
   *
   * @param dm the data model for the table
   * @see #createDefaultColumnModel
   * @see #createDefaultSelectionModel
   */
  public JTable(TableModel dm) {
    this(dm, null, null);
  }

  /**
   * Constructs a <code>JTable</code> that is initialized with
   * <code>dm</code> as the data model, <code>cm</code>
   * as the column model, and a default selection model.
   *
   * @param dm the data model for the table
   * @param cm the column model for the table
   * @see #createDefaultSelectionModel
   */
  public JTable(TableModel dm, TableColumnModel cm) {
    this(dm, cm, null);
  }

  /**
   * Constructs a <code>JTable</code> that is initialized with
   * <code>dm</code> as the data model, <code>cm</code> as the
   * column model, and <code>sm</code> as the selection model.
   * If any of the parameters are <code>null</code> this method
   * will initialize the table with the corresponding default model.
   * The <code>autoCreateColumnsFromModel</code> flag is set to false
   * if <code>cm</code> is non-null, otherwise it is set to true
   * and the column model is populated with suitable
   * <code>TableColumns</code> for the columns in <code>dm</code>.
   *
   * @param dm the data model for the table
   * @param cm the column model for the table
   * @param sm the row selection model for the table
   * @see #createDefaultDataModel
   * @see #createDefaultColumnModel
   * @see #createDefaultSelectionModel
   */
  public JTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) {
    super();
    setLayout(null);

    setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
        JComponent.getManagingFocusForwardTraversalKeys());
    setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
        JComponent.getManagingFocusBackwardTraversalKeys());
    if (cm == null) {
      cm = createDefaultColumnModel();
      autoCreateColumnsFromModel = true;
    }
    setColumnModel(cm);

    if (sm == null) {
      sm = createDefaultSelectionModel();
    }
    setSelectionModel(sm);

    // Set the model last, that way if the autoCreatColumnsFromModel has
    // been set above, we will automatically populate an empty columnModel
    // with suitable columns for the new model.
    if (dm == null) {
      dm = createDefaultDataModel();
    }
    setModel(dm);

    initializeLocalVars();
    updateUI();
  }

  /**
   * Constructs a <code>JTable</code> with <code>numRows</code>
   * and <code>numColumns</code> of empty cells using
   * <code>DefaultTableModel</code>.  The columns will have
   * names of the form "A", "B", "C", etc.
   *
   * @param numRows the number of rows the table holds
   * @param numColumns the number of columns the table holds
   * @see javax.swing.table.DefaultTableModel
   */
  public JTable(int numRows, int numColumns) {
    this(new DefaultTableModel(numRows, numColumns));
  }

  /**
   * Constructs a <code>JTable</code> to display the values in the
   * <code>Vector</code> of <code>Vectors</code>, <code>rowData</code>,
   * with column names, <code>columnNames</code>.  The
   * <code>Vectors</code> contained in <code>rowData</code>
   * should contain the values for that row. In other words,
   * the value of the cell at row 1, column 5 can be obtained
   * with the following code:
   *
   * <pre>((Vector)rowData.elementAt(1)).elementAt(5);</pre>
   * <p>
   *
   * @param rowData the data for the new table
   * @param columnNames names of each column
   */
  public JTable(Vector rowData, Vector columnNames) {
    this(new DefaultTableModel(rowData, columnNames));
  }

  /**
   * Constructs a <code>JTable</code> to display the values in the two dimensional array,
   * <code>rowData</code>, with column names, <code>columnNames</code>.
   * <code>rowData</code> is an array of rows, so the value of the cell at row 1,
   * column 5 can be obtained with the following code:
   *
   * <pre> rowData[1][5]; </pre>
   * <p>
   * All rows must be of the same length as <code>columnNames</code>.
   * <p>
   *
   * @param rowData the data for the new table
   * @param columnNames names of each column
   */
  public JTable(final Object[][] rowData, final Object[] columnNames) {
    this(new AbstractTableModel() {
      public String getColumnName(int column) {
        return columnNames[column].toString();
      }

      public int getRowCount() {
        return rowData.length;
      }

      public int getColumnCount() {
        return columnNames.length;
      }

      public Object getValueAt(int row, int col) {
        return rowData[row][col];
      }

      public boolean isCellEditable(int row, int column) {
        return true;
      }

      public void setValueAt(Object value, int row, int col) {
        rowData[row][col] = value;
        fireTableCellUpdated(row, col);
      }
    });
  }

  /**
   * Calls the <code>configureEnclosingScrollPane</code> method.
   *
   * @see #configureEnclosingScrollPane
   */
  public void addNotify() {
    super.addNotify();
    configureEnclosingScrollPane();
  }

  /**
   * If this <code>JTable</code> is the <code>viewportView</code> of an enclosing
   * <code>JScrollPane</code> (the usual situation), configure this <code>ScrollPane</code> by,
   * amongst other things, installing the table's <code>tableHeader</code> as the
   * <code>columnHeaderView</code> of the scroll pane. When a <code>JTable</code> is added to a
   * <code>JScrollPane</code> in the usual way, using <code>new JScrollPane(myTable)</code>,
   * <code>addNotify</code> is called in the <code>JTable</code> (when the table is added to the
   * viewport). <code>JTable</code>'s <code>addNotify</code> method in turn calls this method, which
   * is protected so that this default installation procedure can be overridden by a subclass.
   *
   * @see #addNotify
   */
  protected void configureEnclosingScrollPane() {
    Container parent = SwingUtilities.getUnwrappedParent(this);
    if (parent instanceof JViewport) {
      JViewport port = (JViewport) parent;
      Container gp = port.getParent();
      if (gp instanceof JScrollPane) {
        JScrollPane scrollPane = (JScrollPane) gp;
        // Make certain we are the viewPort's view and not, for
        // example, the rowHeaderView of the scrollPane -
        // an implementor of fixed columns might do this.
        JViewport viewport = scrollPane.getViewport();
        if (viewport == null ||
            SwingUtilities.getUnwrappedView(viewport) != this) {
          return;
        }
        scrollPane.setColumnHeaderView(getTableHeader());
        // configure the scrollpane for any LAF dependent settings
        configureEnclosingScrollPaneUI();
      }
    }
  }

  /**
   * This is a sub-part of configureEnclosingScrollPane() that configures
   * anything on the scrollpane that may change when the look and feel
   * changes. It needed to be split out from configureEnclosingScrollPane() so
   * that it can be called from updateUI() when the LAF changes without
   * causing the regression found in bug 6687962. This was because updateUI()
   * is called from the constructor which then caused
   * configureEnclosingScrollPane() to be called by the constructor which
   * changes its contract for any subclass that overrides it. So by splitting
   * it out in this way configureEnclosingScrollPaneUI() can be called both
   * from configureEnclosingScrollPane() and updateUI() in a safe manor.
   */
  private void configureEnclosingScrollPaneUI() {
    Container parent = SwingUtilities.getUnwrappedParent(this);
    if (parent instanceof JViewport) {
      JViewport port = (JViewport) parent;
      Container gp = port.getParent();
      if (gp instanceof JScrollPane) {
        JScrollPane scrollPane = (JScrollPane) gp;
        // Make certain we are the viewPort's view and not, for
        // example, the rowHeaderView of the scrollPane -
        // an implementor of fixed columns might do this.
        JViewport viewport = scrollPane.getViewport();
        if (viewport == null ||
            SwingUtilities.getUnwrappedView(viewport) != this) {
          return;
        }
        //  scrollPane.getViewport().setBackingStoreEnabled(true);
        Border border = scrollPane.getBorder();
        if (border == null || border instanceof UIResource) {
          Border scrollPaneBorder =
              UIManager.getBorder("Table.scrollPaneBorder");
          if (scrollPaneBorder != null) {
            scrollPane.setBorder(scrollPaneBorder);
          }
        }
        // add JScrollBar corner component if available from LAF and not already set by the user
        Component corner =
            scrollPane.getCorner(JScrollPane.UPPER_TRAILING_CORNER);
        if (corner == null || corner instanceof UIResource) {
          corner = null;
          try {
            corner = (Component) UIManager.get(
                "Table.scrollPaneCornerComponent");
          } catch (Exception e) {
            // just ignore and don't set corner
          }
          scrollPane.setCorner(JScrollPane.UPPER_TRAILING_CORNER,
              corner);
        }
      }
    }
  }

  /**
   * Calls the <code>unconfigureEnclosingScrollPane</code> method.
   *
   * @see #unconfigureEnclosingScrollPane
   */
  public void removeNotify() {
    KeyboardFocusManager.getCurrentKeyboardFocusManager().
        removePropertyChangeListener("permanentFocusOwner", editorRemover);
    editorRemover = null;
    unconfigureEnclosingScrollPane();
    super.removeNotify();
  }

  /**
   * Reverses the effect of <code>configureEnclosingScrollPane</code>
   * by replacing the <code>columnHeaderView</code> of the enclosing
   * scroll pane with <code>null</code>. <code>JTable</code>'s
   * <code>removeNotify</code> method calls
   * this method, which is protected so that this default uninstallation
   * procedure can be overridden by a subclass.
   *
   * @see #removeNotify
   * @see #configureEnclosingScrollPane
   * @since 1.3
   */
  protected void unconfigureEnclosingScrollPane() {
    Container parent = SwingUtilities.getUnwrappedParent(this);
    if (parent instanceof JViewport) {
      JViewport port = (JViewport) parent;
      Container gp = port.getParent();
      if (gp instanceof JScrollPane) {
        JScrollPane scrollPane = (JScrollPane) gp;
        // Make certain we are the viewPort's view and not, for
        // example, the rowHeaderView of the scrollPane -
        // an implementor of fixed columns might do this.
        JViewport viewport = scrollPane.getViewport();
        if (viewport == null ||
            SwingUtilities.getUnwrappedView(viewport) != this) {
          return;
        }
        scrollPane.setColumnHeaderView(null);
        // remove ScrollPane corner if one was added by the LAF
        Component corner =
            scrollPane.getCorner(JScrollPane.UPPER_TRAILING_CORNER);
        if (corner instanceof UIResource) {
          scrollPane.setCorner(JScrollPane.UPPER_TRAILING_CORNER,
              null);
        }
      }
    }
  }

  void setUIProperty(String propertyName, Object value) {
    if (propertyName == "rowHeight") {
      if (!isRowHeightSet) {
        setRowHeight(((Number) value).intValue());
        isRowHeightSet = false;
      }
      return;
    }
    super.setUIProperty(propertyName, value);
  }

//
// Static Methods
//

  /**
   * Equivalent to <code>new JScrollPane(aTable)</code>.
   *
   * @deprecated As of Swing version 1.0.2, replaced by <code>new JScrollPane(aTable)</code>.
   */
  @Deprecated
  static public JScrollPane createScrollPaneForTable(JTable aTable) {
    return new JScrollPane(aTable);
  }

//
// Table Attributes
//

  /**
   * Sets the <code>tableHeader</code> working with this <code>JTable</code> to
   * <code>newHeader</code>. It is legal to have a <code>null</code> <code>tableHeader</code>.
   *
   * @param tableHeader new tableHeader
   * @beaninfo bound: true description: The JTableHeader instance which renders the column headers.
   * @see #getTableHeader
   */
  public void setTableHeader(JTableHeader tableHeader) {
    if (this.tableHeader != tableHeader) {
      JTableHeader old = this.tableHeader;
      // Release the old header
      if (old != null) {
        old.setTable(null);
      }
      this.tableHeader = tableHeader;
      if (tableHeader != null) {
        tableHeader.setTable(this);
      }
      firePropertyChange("tableHeader", old, tableHeader);
    }
  }

  /**
   * Returns the <code>tableHeader</code> used by this <code>JTable</code>.
   *
   * @return the <code>tableHeader</code> used by this table
   * @see #setTableHeader
   */
  public JTableHeader getTableHeader() {
    return tableHeader;
  }

  /**
   * Sets the height, in pixels, of all cells to <code>rowHeight</code>,
   * revalidates, and repaints.
   * The height of the cells will be equal to the row height minus
   * the row margin.
   *
   * @param rowHeight new row height
   * @throws IllegalArgumentException if <code>rowHeight</code> is less than 1
   * @beaninfo bound: true description: The height of the specified row.
   * @see #getRowHeight
   */
  public void setRowHeight(int rowHeight) {
    if (rowHeight <= 0) {
      throw new IllegalArgumentException("New row height less than 1");
    }
    int old = this.rowHeight;
    this.rowHeight = rowHeight;
    rowModel = null;
    if (sortManager != null) {
      sortManager.modelRowSizes = null;
    }
    isRowHeightSet = true;
    resizeAndRepaint();
    firePropertyChange("rowHeight", old, rowHeight);
  }

  /**
   * Returns the height of a table row, in pixels.
   *
   * @return the height in pixels of a table row
   * @see #setRowHeight
   */
  public int getRowHeight() {
    return rowHeight;
  }

  private SizeSequence getRowModel() {
    if (rowModel == null) {
      rowModel = new SizeSequence(getRowCount(), getRowHeight());
    }
    return rowModel;
  }

  /**
   * Sets the height for <code>row</code> to <code>rowHeight</code>,
   * revalidates, and repaints. The height of the cells in this row
   * will be equal to the row height minus the row margin.
   *
   * @param row the row whose height is being changed
   * @param rowHeight new row height, in pixels
   * @throws IllegalArgumentException if <code>rowHeight</code> is less than 1
   * @beaninfo bound: true description: The height in pixels of the cells in <code>row</code>
   * @since 1.3
   */
  public void setRowHeight(int row, int rowHeight) {
    if (rowHeight <= 0) {
      throw new IllegalArgumentException("New row height less than 1");
    }
    getRowModel().setSize(row, rowHeight);
    if (sortManager != null) {
      sortManager.setViewRowHeight(row, rowHeight);
    }
    resizeAndRepaint();
  }

  /**
   * Returns the height, in pixels, of the cells in <code>row</code>.
   *
   * @param row the row whose height is to be returned
   * @return the height, in pixels, of the cells in the row
   * @since 1.3
   */
  public int getRowHeight(int row) {
    return (rowModel == null) ? getRowHeight() : rowModel.getSize(row);
  }

  /**
   * Sets the amount of empty space between cells in adjacent rows.
   *
   * @param rowMargin the number of pixels between cells in a row
   * @beaninfo bound: true description: The amount of space between cells.
   * @see #getRowMargin
   */
  public void setRowMargin(int rowMargin) {
    int old = this.rowMargin;
    this.rowMargin = rowMargin;
    resizeAndRepaint();
    firePropertyChange("rowMargin", old, rowMargin);
  }

  /**
   * Gets the amount of empty space, in pixels, between cells. Equivalent to:
   * <code>getIntercellSpacing().height</code>.
   *
   * @return the number of pixels between cells in a row
   * @see #setRowMargin
   */
  public int getRowMargin() {
    return rowMargin;
  }

  /**
   * Sets the <code>rowMargin</code> and the <code>columnMargin</code> --
   * the height and width of the space between cells -- to
   * <code>intercellSpacing</code>.
   *
   * @param intercellSpacing a <code>Dimension</code> specifying the new width and height between
   * cells
   * @beaninfo description: The spacing between the cells, drawn in the background color of the
   * JTable.
   * @see #getIntercellSpacing
   */
  public void setIntercellSpacing(Dimension intercellSpacing) {
    // Set the rowMargin here and columnMargin in the TableColumnModel
    setRowMargin(intercellSpacing.height);
    getColumnModel().setColumnMargin(intercellSpacing.width);

    resizeAndRepaint();
  }

  /**
   * Returns the horizontal and vertical space between cells.
   * The default spacing is look and feel dependent.
   *
   * @return the horizontal and vertical spacing between cells
   * @see #setIntercellSpacing
   */
  public Dimension getIntercellSpacing() {
    return new Dimension(getColumnModel().getColumnMargin(), rowMargin);
  }

  /**
   * Sets the color used to draw grid lines to <code>gridColor</code> and redisplays.
   * The default color is look and feel dependent.
   *
   * @param gridColor the new color of the grid lines
   * @throws IllegalArgumentException if <code>gridColor</code> is <code>null</code>
   * @beaninfo bound: true description: The grid color.
   * @see #getGridColor
   */
  public void setGridColor(Color gridColor) {
    if (gridColor == null) {
      throw new IllegalArgumentException("New color is null");
    }
    Color old = this.gridColor;
    this.gridColor = gridColor;
    firePropertyChange("gridColor", old, gridColor);
    // Redraw
    repaint();
  }

  /**
   * Returns the color used to draw grid lines.
   * The default color is look and feel dependent.
   *
   * @return the color used to draw grid lines
   * @see #setGridColor
   */
  public Color getGridColor() {
    return gridColor;
  }

  /**
   * Sets whether the table draws grid lines around cells.
   * If <code>showGrid</code> is true it does; if it is false it doesn't.
   * There is no <code>getShowGrid</code> method as this state is held
   * in two variables -- <code>showHorizontalLines</code> and <code>showVerticalLines</code> --
   * each of which can be queried independently.
   *
   * @param showGrid true if table view should draw grid lines
   * @beaninfo description: The color used to draw the grid lines.
   * @see #setShowVerticalLines
   * @see #setShowHorizontalLines
   */
  public void setShowGrid(boolean showGrid) {
    setShowHorizontalLines(showGrid);
    setShowVerticalLines(showGrid);

    // Redraw
    repaint();
  }

  /**
   * Sets whether the table draws horizontal lines between cells.
   * If <code>showHorizontalLines</code> is true it does; if it is false it doesn't.
   *
   * @param showHorizontalLines true if table view should draw horizontal lines
   * @beaninfo bound: true description: Whether horizontal lines should be drawn in between the
   * cells.
   * @see #getShowHorizontalLines
   * @see #setShowGrid
   * @see #setShowVerticalLines
   */
  public void setShowHorizontalLines(boolean showHorizontalLines) {
    boolean old = this.showHorizontalLines;
    this.showHorizontalLines = showHorizontalLines;
    firePropertyChange("showHorizontalLines", old, showHorizontalLines);

    // Redraw
    repaint();
  }

  /**
   * Sets whether the table draws vertical lines between cells.
   * If <code>showVerticalLines</code> is true it does; if it is false it doesn't.
   *
   * @param showVerticalLines true if table view should draw vertical lines
   * @beaninfo bound: true description: Whether vertical lines should be drawn in between the
   * cells.
   * @see #getShowVerticalLines
   * @see #setShowGrid
   * @see #setShowHorizontalLines
   */
  public void setShowVerticalLines(boolean showVerticalLines) {
    boolean old = this.showVerticalLines;
    this.showVerticalLines = showVerticalLines;
    firePropertyChange("showVerticalLines", old, showVerticalLines);
    // Redraw
    repaint();
  }

  /**
   * Returns true if the table draws horizontal lines between cells, false if it
   * doesn't. The default value is look and feel dependent.
   *
   * @return true if the table draws horizontal lines between cells, false if it doesn't
   * @see #setShowHorizontalLines
   */
  public boolean getShowHorizontalLines() {
    return showHorizontalLines;
  }

  /**
   * Returns true if the table draws vertical lines between cells, false if it
   * doesn't. The default value is look and feel dependent.
   *
   * @return true if the table draws vertical lines between cells, false if it doesn't
   * @see #setShowVerticalLines
   */
  public boolean getShowVerticalLines() {
    return showVerticalLines;
  }

  /**
   * Sets the table's auto resize mode when the table is resized.  For further
   * information on how the different resize modes work, see
   * {@link #doLayout}.
   *
   * @param mode One of 5 legal values: AUTO_RESIZE_OFF, AUTO_RESIZE_NEXT_COLUMN,
   * AUTO_RESIZE_SUBSEQUENT_COLUMNS, AUTO_RESIZE_LAST_COLUMN, AUTO_RESIZE_ALL_COLUMNS
   * @beaninfo bound: true description: Whether the columns should adjust themselves automatically.
   * enum: AUTO_RESIZE_OFF                JTable.AUTO_RESIZE_OFF AUTO_RESIZE_NEXT_COLUMN
   * JTable.AUTO_RESIZE_NEXT_COLUMN AUTO_RESIZE_SUBSEQUENT_COLUMNS JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS
   * AUTO_RESIZE_LAST_COLUMN        JTable.AUTO_RESIZE_LAST_COLUMN AUTO_RESIZE_ALL_COLUMNS
   * JTable.AUTO_RESIZE_ALL_COLUMNS
   * @see #getAutoResizeMode
   * @see #doLayout
   */
  public void setAutoResizeMode(int mode) {
    if ((mode == AUTO_RESIZE_OFF) ||
        (mode == AUTO_RESIZE_NEXT_COLUMN) ||
        (mode == AUTO_RESIZE_SUBSEQUENT_COLUMNS) ||
        (mode == AUTO_RESIZE_LAST_COLUMN) ||
        (mode == AUTO_RESIZE_ALL_COLUMNS)) {
      int old = autoResizeMode;
      autoResizeMode = mode;
      resizeAndRepaint();
      if (tableHeader != null) {
        tableHeader.resizeAndRepaint();
      }
      firePropertyChange("autoResizeMode", old, autoResizeMode);
    }
  }

  /**
   * Returns the auto resize mode of the table.  The default mode
   * is AUTO_RESIZE_SUBSEQUENT_COLUMNS.
   *
   * @return the autoResizeMode of the table
   * @see #setAutoResizeMode
   * @see #doLayout
   */
  public int getAutoResizeMode() {
    return autoResizeMode;
  }

  /**
   * Sets this table's <code>autoCreateColumnsFromModel</code> flag.
   * This method calls <code>createDefaultColumnsFromModel</code> if
   * <code>autoCreateColumnsFromModel</code> changes from false to true.
   *
   * @param autoCreateColumnsFromModel true if <code>JTable</code> should automatically create
   * columns
   * @beaninfo bound: true description: Automatically populates the columnModel when a new
   * TableModel is submitted.
   * @see #getAutoCreateColumnsFromModel
   * @see #createDefaultColumnsFromModel
   */
  public void setAutoCreateColumnsFromModel(boolean autoCreateColumnsFromModel) {
    if (this.autoCreateColumnsFromModel != autoCreateColumnsFromModel) {
      boolean old = this.autoCreateColumnsFromModel;
      this.autoCreateColumnsFromModel = autoCreateColumnsFromModel;
      if (autoCreateColumnsFromModel) {
        createDefaultColumnsFromModel();
      }
      firePropertyChange("autoCreateColumnsFromModel", old, autoCreateColumnsFromModel);
    }
  }

  /**
   * Determines whether the table will create default columns from the model.
   * If true, <code>setModel</code> will clear any existing columns and
   * create new columns from the new model.  Also, if the event in
   * the <code>tableChanged</code> notification specifies that the
   * entire table changed, then the columns will be rebuilt.
   * The default is true.
   *
   * @return the autoCreateColumnsFromModel of the table
   * @see #setAutoCreateColumnsFromModel
   * @see #createDefaultColumnsFromModel
   */
  public boolean getAutoCreateColumnsFromModel() {
    return autoCreateColumnsFromModel;
  }

  /**
   * Creates default columns for the table from
   * the data model using the <code>getColumnCount</code> method
   * defined in the <code>TableModel</code> interface.
   * <p>
   * Clears any existing columns before creating the
   * new columns based on information from the model.
   *
   * @see #getAutoCreateColumnsFromModel
   */
  public void createDefaultColumnsFromModel() {
    TableModel m = getModel();
    if (m != null) {
      // Remove any current columns
      TableColumnModel cm = getColumnModel();
      while (cm.getColumnCount() > 0) {
        cm.removeColumn(cm.getColumn(0));
      }

      // Create new columns from the data model info
      for (int i = 0; i < m.getColumnCount(); i++) {
        TableColumn newColumn = new TableColumn(i);
        addColumn(newColumn);
      }
    }
  }

  /**
   * Sets a default cell renderer to be used if no renderer has been set in
   * a <code>TableColumn</code>. If renderer is <code>null</code>,
   * removes the default renderer for this column class.
   *
   * @param columnClass set the default cell renderer for this columnClass
   * @param renderer default cell renderer to be used for this columnClass
   * @see #getDefaultRenderer
   * @see #setDefaultEditor
   */
  public void setDefaultRenderer(Class<?> columnClass, TableCellRenderer renderer) {
    if (renderer != null) {
      defaultRenderersByColumnClass.put(columnClass, renderer);
    } else {
      defaultRenderersByColumnClass.remove(columnClass);
    }
  }

  /**
   * Returns the cell renderer to be used when no renderer has been set in a
   * <code>TableColumn</code>. During the rendering of cells the renderer is fetched from a
   * <code>Hashtable</code> of entries according to the class of the cells in the column. If there
   * is no entry for this <code>columnClass</code> the method returns the entry for the most
   * specific superclass. The <code>JTable</code> installs entries for <code>Object</code>,
   * <code>Number</code>, and <code>Boolean</code>, all of which can be modified or replaced.
   *
   * @param columnClass return the default cell renderer for this columnClass
   * @return the renderer for this columnClass
   * @see #setDefaultRenderer
   * @see #getColumnClass
   */
  public TableCellRenderer getDefaultRenderer(Class<?> columnClass) {
    if (columnClass == null) {
      return null;
    } else {
      Object renderer = defaultRenderersByColumnClass.get(columnClass);
      if (renderer != null) {
        return (TableCellRenderer) renderer;
      } else {
        Class c = columnClass.getSuperclass();
        if (c == null && columnClass != Object.class) {
          c = Object.class;
        }
        return getDefaultRenderer(c);
      }
    }
  }

  /**
   * Sets a default cell editor to be used if no editor has been set in
   * a <code>TableColumn</code>. If no editing is required in a table, or a
   * particular column in a table, uses the <code>isCellEditable</code>
   * method in the <code>TableModel</code> interface to ensure that this
   * <code>JTable</code> will not start an editor in these columns.
   * If editor is <code>null</code>, removes the default editor for this
   * column class.
   *
   * @param columnClass set the default cell editor for this columnClass
   * @param editor default cell editor to be used for this columnClass
   * @see TableModel#isCellEditable
   * @see #getDefaultEditor
   * @see #setDefaultRenderer
   */
  public void setDefaultEditor(Class<?> columnClass, TableCellEditor editor) {
    if (editor != null) {
      defaultEditorsByColumnClass.put(columnClass, editor);
    } else {
      defaultEditorsByColumnClass.remove(columnClass);
    }
  }

  /**
   * Returns the editor to be used when no editor has been set in a <code>TableColumn</code>. During
   * the editing of cells the editor is fetched from a <code>Hashtable</code> of entries according
   * to the class of the cells in the column. If there is no entry for this <code>columnClass</code>
   * the method returns the entry for the most specific superclass. The <code>JTable</code> installs
   * entries for <code>Object</code>, <code>Number</code>, and <code>Boolean</code>, all of which
   * can be modified or replaced.
   *
   * @param columnClass return the default cell editor for this columnClass
   * @return the default cell editor to be used for this columnClass
   * @see #setDefaultEditor
   * @see #getColumnClass
   */
  public TableCellEditor getDefaultEditor(Class<?> columnClass) {
    if (columnClass == null) {
      return null;
    } else {
      Object editor = defaultEditorsByColumnClass.get(columnClass);
      if (editor != null) {
        return (TableCellEditor) editor;
      } else {
        return getDefaultEditor(columnClass.getSuperclass());
      }
    }
  }

  /**
   * Turns on or off automatic drag handling. In order to enable automatic
   * drag handling, this property should be set to {@code true}, and the
   * table's {@code TransferHandler} needs to be {@code non-null}.
   * The default value of the {@code dragEnabled} property is {@code false}.
   * <p>
   * The job of honoring this property, and recognizing a user drag gesture,
   * lies with the look and feel implementation, and in particular, the table's
   * {@code TableUI}. When automatic drag handling is enabled, most look and
   * feels (including those that subclass {@code BasicLookAndFeel}) begin a
   * drag and drop operation whenever the user presses the mouse button over
   * an item (in single selection mode) or a selection (in other selection
   * modes) and then moves the mouse a few pixels. Setting this property to
   * {@code true} can therefore have a subtle effect on how selections behave.
   * <p>
   * If a look and feel is used that ignores this property, you can still
   * begin a drag and drop operation by calling {@code exportAsDrag} on the
   * table's {@code TransferHandler}.
   *
   * @param b whether or not to enable automatic drag handling
   * @throws HeadlessException if <code>b</code> is <code>true</code> and
   * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>
   * @beaninfo description: determines whether automatic drag handling is enabled bound: false
   * @see java.awt.GraphicsEnvironment#isHeadless
   * @see #getDragEnabled
   * @see #setTransferHandler
   * @see TransferHandler
   * @since 1.4
   */
  public void setDragEnabled(boolean b) {
    if (b && GraphicsEnvironment.isHeadless()) {
      throw new HeadlessException();
    }
    dragEnabled = b;
  }

  /**
   * Returns whether or not automatic drag handling is enabled.
   *
   * @return the value of the {@code dragEnabled} property
   * @see #setDragEnabled
   * @since 1.4
   */
  public boolean getDragEnabled() {
    return dragEnabled;
  }

  /**
   * Sets the drop mode for this component. For backward compatibility,
   * the default for this property is <code>DropMode.USE_SELECTION</code>.
   * Usage of one of the other modes is recommended, however, for an
   * improved user experience. <code>DropMode.ON</code>, for instance,
   * offers similar behavior of showing items as selected, but does so without
   * affecting the actual selection in the table.
   * <p>
   * <code>JTable</code> supports the following drop modes:
   * <ul>
   * <li><code>DropMode.USE_SELECTION</code></li>
   * <li><code>DropMode.ON</code></li>
   * <li><code>DropMode.INSERT</code></li>
   * <li><code>DropMode.INSERT_ROWS</code></li>
   * <li><code>DropMode.INSERT_COLS</code></li>
   * <li><code>DropMode.ON_OR_INSERT</code></li>
   * <li><code>DropMode.ON_OR_INSERT_ROWS</code></li>
   * <li><code>DropMode.ON_OR_INSERT_COLS</code></li>
   * </ul>
   * <p>
   * The drop mode is only meaningful if this component has a
   * <code>TransferHandler</code> that accepts drops.
   *
   * @param dropMode the drop mode to use
   * @throws IllegalArgumentException if the drop mode is unsupported or <code>null</code>
   * @see #getDropMode
   * @see #getDropLocation
   * @see #setTransferHandler
   * @see TransferHandler
   * @since 1.6
   */
  public final void setDropMode(DropMode dropMode) {
    if (dropMode != null) {
      switch (dropMode) {
        case USE_SELECTION:
        case ON:
        case INSERT:
        case INSERT_ROWS:
        case INSERT_COLS:
        case ON_OR_INSERT:
        case ON_OR_INSERT_ROWS:
        case ON_OR_INSERT_COLS:
          this.dropMode = dropMode;
          return;
      }
    }

    throw new IllegalArgumentException(dropMode + ": Unsupported drop mode for table");
  }

  /**
   * Returns the drop mode for this component.
   *
   * @return the drop mode for this component
   * @see #setDropMode
   * @since 1.6
   */
  public final DropMode getDropMode() {
    return dropMode;
  }

  /**
   * Calculates a drop location in this component, representing where a
   * drop at the given point should insert data.
   *
   * @param p the point to calculate a drop location for
   * @return the drop location, or <code>null</code>
   */
  DropLocation dropLocationForPoint(Point p) {
    DropLocation location = null;

    int row = rowAtPoint(p);
    int col = columnAtPoint(p);
    boolean outside = Boolean.TRUE == getClientProperty("Table.isFileList")
        && SwingUtilities2.pointOutsidePrefSize(this, row, col, p);

    Rectangle rect = getCellRect(row, col, true);
    Section xSection, ySection;
    boolean between = false;
    boolean ltr = getComponentOrientation().isLeftToRight();

    switch (dropMode) {
      case USE_SELECTION:
      case ON:
        if (row == -1 || col == -1 || outside) {
          location = new DropLocation(p, -1, -1, false, false);
        } else {
          location = new DropLocation(p, row, col, false, false);
        }
        break;
      case INSERT:
        if (row == -1 && col == -1) {
          location = new DropLocation(p, 0, 0, true, true);
          break;
        }

        xSection = SwingUtilities2.liesInHorizontal(rect, p, ltr, true);

        if (row == -1) {
          if (xSection == LEADING) {
            location = new DropLocation(p, getRowCount(), col, true, true);
          } else if (xSection == TRAILING) {
            location = new DropLocation(p, getRowCount(), col + 1, true, true);
          } else {
            location = new DropLocation(p, getRowCount(), col, true, false);
          }
        } else if (xSection == LEADING || xSection == TRAILING) {
          ySection = SwingUtilities2.liesInVertical(rect, p, true);
          if (ySection == LEADING) {
            between = true;
          } else if (ySection == TRAILING) {
            row++;
            between = true;
          }

          location = new DropLocation(p, row,
              xSection == TRAILING ? col + 1 : col,
              between, true);
        } else {
          if (SwingUtilities2.liesInVertical(rect, p, false) == TRAILING) {
            row++;
          }

          location = new DropLocation(p, row, col, true, false);
        }

        break;
      case INSERT_ROWS:
        if (row == -1 && col == -1) {
          location = new DropLocation(p, -1, -1, false, false);
          break;
        }

        if (row == -1) {
          location = new DropLocation(p, getRowCount(), col, true, false);
          break;
        }

        if (SwingUtilities2.liesInVertical(rect, p, false) == TRAILING) {
          row++;
        }

        location = new DropLocation(p, row, col, true, false);
        break;
      case ON_OR_INSERT_ROWS:
        if (row == -1 && col == -1) {
          location = new DropLocation(p, -1, -1, false, false);
          break;
        }

        if (row == -1) {
          location = new DropLocation(p, getRowCount(), col, true, false);
          break;
        }

        ySection = SwingUtilities2.liesInVertical(rect, p, true);
        if (ySection == LEADING) {
          between = true;
        } else if (ySection == TRAILING) {
          row++;
          between = true;
        }

        location = new DropLocation(p, row, col, between, false);
        break;
      case INSERT_COLS:
        if (row == -1) {
          location = new DropLocation(p, -1, -1, false, false);
          break;
        }

        if (col == -1) {
          location = new DropLocation(p, getColumnCount(), col, false, true);
          break;
        }

        if (SwingUtilities2.liesInHorizontal(rect, p, ltr, false) == TRAILING) {
          col++;
        }

        location = new DropLocation(p, row, col, false, true);
        break;
      case ON_OR_INSERT_COLS:
        if (row == -1) {
          location = new DropLocation(p, -1, -1, false, false);
          break;
        }

        if (col == -1) {
          location = new DropLocation(p, row, getColumnCount(), false, true);
          break;
        }

        xSection = SwingUtilities2.liesInHorizontal(rect, p, ltr, true);
        if (xSection == LEADING) {
          between = true;
        } else if (xSection == TRAILING) {
          col++;
          between = true;
        }

        location = new DropLocation(p, row, col, false, between);
        break;
      case ON_OR_INSERT:
        if (row == -1 && col == -1) {
          location = new DropLocation(p, 0, 0, true, true);
          break;
        }

        xSection = SwingUtilities2.liesInHorizontal(rect, p, ltr, true);

        if (row == -1) {
          if (xSection == LEADING) {
            location = new DropLocation(p, getRowCount(), col, true, true);
          } else if (xSection == TRAILING) {
            location = new DropLocation(p, getRowCount(), col + 1, true, true);
          } else {
            location = new DropLocation(p, getRowCount(), col, true, false);
          }

          break;
        }

        ySection = SwingUtilities2.liesInVertical(rect, p, true);
        if (ySection == LEADING) {
          between = true;
        } else if (ySection == TRAILING) {
          row++;
          between = true;
        }

        location = new DropLocation(p, row,
            xSection == TRAILING ? col + 1 : col,
            between,
            xSection != MIDDLE);

        break;
      default:
        assert false : "Unexpected drop mode";
    }

    return location;
  }

  /**
   * Called to set or clear the drop location during a DnD operation.
   * In some cases, the component may need to use it's internal selection
   * temporarily to indicate the drop location. To help facilitate this,
   * this method returns and accepts as a parameter a state object.
   * This state object can be used to store, and later restore, the selection
   * state. Whatever this method returns will be passed back to it in
   * future calls, as the state parameter. If it wants the DnD system to
   * continue storing the same state, it must pass it back every time.
   * Here's how this is used:
   * <p>
   * Let's say that on the first call to this method the component decides
   * to save some state (because it is about to use the selection to show
   * a drop index). It can return a state object to the caller encapsulating
   * any saved selection state. On a second call, let's say the drop location
   * is being changed to something else. The component doesn't need to
   * restore anything yet, so it simply passes back the same state object
   * to have the DnD system continue storing it. Finally, let's say this
   * method is messaged with <code>null</code>. This means DnD
   * is finished with this component for now, meaning it should restore
   * state. At this point, it can use the state parameter to restore
   * said state, and of course return <code>null</code> since there's
   * no longer anything to store.
   *
   * @param location the drop location (as calculated by <code>dropLocationForPoint</code>) or
   * <code>null</code> if there's no longer a valid drop location
   * @param state the state object saved earlier for this component, or <code>null</code>
   * @param forDrop whether or not the method is being called because an actual drop occurred
   * @return any saved state for this component, or <code>null</code> if none
   */
  Object setDropLocation(TransferHandler.DropLocation location,
      Object state,
      boolean forDrop) {

    Object retVal = null;
    DropLocation tableLocation = (DropLocation) location;

    if (dropMode == DropMode.USE_SELECTION) {
      if (tableLocation == null) {
        if (!forDrop && state != null) {
          clearSelection();

          int[] rows = ((int[][]) state)[0];
          int[] cols = ((int[][]) state)[1];
          int[] anchleads = ((int[][]) state)[2];

          for (int row : rows) {
            addRowSelectionInterval(row, row);
          }

          for (int col : cols) {
            addColumnSelectionInterval(col, col);
          }

          SwingUtilities2.setLeadAnchorWithoutSelection(
              getSelectionModel(), anchleads[1], anchleads[0]);

          SwingUtilities2.setLeadAnchorWithoutSelection(
              getColumnModel().getSelectionModel(),
              anchleads[3], anchleads[2]);
        }
      } else {
        if (dropLocation == null) {
          retVal = new int[][]{
              getSelectedRows(),
              getSelectedColumns(),
              {getAdjustedIndex(getSelectionModel()
                  .getAnchorSelectionIndex(), true),
                  getAdjustedIndex(getSelectionModel()
                      .getLeadSelectionIndex(), true),
                  getAdjustedIndex(getColumnModel().getSelectionModel()
                      .getAnchorSelectionIndex(), false),
                  getAdjustedIndex(getColumnModel().getSelectionModel()
                      .getLeadSelectionIndex(), false)}};
        } else {
          retVal = state;
        }

        if (tableLocation.getRow() == -1) {
          clearSelectionAndLeadAnchor();
        } else {
          setRowSelectionInterval(tableLocation.getRow(),
              tableLocation.getRow());
          setColumnSelectionInterval(tableLocation.getColumn(),
              tableLocation.getColumn());
        }
      }
    }

    DropLocation old = dropLocation;
    dropLocation = tableLocation;
    firePropertyChange("dropLocation", old, dropLocation);

    return retVal;
  }

  /**
   * Returns the location that this component should visually indicate
   * as the drop location during a DnD operation over the component,
   * or {@code null} if no location is to currently be shown.
   * <p>
   * This method is not meant for querying the drop location
   * from a {@code TransferHandler}, as the drop location is only
   * set after the {@code TransferHandler}'s <code>canImport</code>
   * has returned and has allowed for the location to be shown.
   * <p>
   * When this property changes, a property change event with
   * name "dropLocation" is fired by the component.
   *
   * @return the drop location
   * @see #setDropMode
   * @see TransferHandler#canImport(TransferHandler.TransferSupport)
   * @since 1.6
   */
  public final DropLocation getDropLocation() {
    return dropLocation;
  }

  /**
   * Specifies whether a {@code RowSorter} should be created for the
   * table whenever its model changes.
   * <p>
   * When {@code setAutoCreateRowSorter(true)} is invoked, a {@code
   * TableRowSorter} is immediately created and installed on the
   * table.  While the {@code autoCreateRowSorter} property remains
   * {@code true}, every time the model is changed, a new {@code
   * TableRowSorter} is created and set as the table's row sorter.
   * The default value for the {@code autoCreateRowSorter}
   * property is {@code false}.
   *
   * @param autoCreateRowSorter whether or not a {@code RowSorter} should be automatically created
   * @beaninfo bound: true preferred: true description: Whether or not to turn on sorting by
   * default.
   * @see javax.swing.table.TableRowSorter
   * @since 1.6
   */
  public void setAutoCreateRowSorter(boolean autoCreateRowSorter) {
    boolean oldValue = this.autoCreateRowSorter;
    this.autoCreateRowSorter = autoCreateRowSorter;
    if (autoCreateRowSorter) {
      setRowSorter(new TableRowSorter<TableModel>(getModel()));
    }
    firePropertyChange("autoCreateRowSorter", oldValue,
        autoCreateRowSorter);
  }

  /**
   * Returns {@code true} if whenever the model changes, a new
   * {@code RowSorter} should be created and installed
   * as the table's sorter; otherwise, returns {@code false}.
   *
   * @return true if a {@code RowSorter} should be created when the model changes
   * @since 1.6
   */
  public boolean getAutoCreateRowSorter() {
    return autoCreateRowSorter;
  }

  /**
   * Specifies whether the selection should be updated after sorting.
   * If true, on sorting the selection is reset such that
   * the same rows, in terms of the model, remain selected.  The default
   * is true.
   *
   * @param update whether or not to update the selection on sorting
   * @beaninfo bound: true expert: true description: Whether or not to update the selection on
   * sorting
   * @since 1.6
   */
  public void setUpdateSelectionOnSort(boolean update) {
    if (updateSelectionOnSort != update) {
      updateSelectionOnSort = update;
      firePropertyChange("updateSelectionOnSort", !update, update);
    }
  }

  /**
   * Returns true if the selection should be updated after sorting.
   *
   * @return whether to update the selection on a sort
   * @since 1.6
   */
  public boolean getUpdateSelectionOnSort() {
    return updateSelectionOnSort;
  }

  /**
   * Sets the <code>RowSorter</code>.  <code>RowSorter</code> is used
   * to provide sorting and filtering to a <code>JTable</code>.
   * <p>
   * This method clears the selection and resets any variable row heights.
   * <p>
   * This method fires a <code>PropertyChangeEvent</code> when appropriate,
   * with the property name <code>"rowSorter"</code>.  For
   * backward-compatibility, this method fires an additional event with the
   * property name <code>"sorter"</code>.
   * <p>
   * If the underlying model of the <code>RowSorter</code> differs from
   * that of this <code>JTable</code> undefined behavior will result.
   *
   * @param sorter the <code>RowSorter</code>; <code>null</code> turns sorting off
   * @beaninfo bound: true description: The table's RowSorter
   * @see javax.swing.table.TableRowSorter
   * @since 1.6
   */
  public void setRowSorter(RowSorter<? extends TableModel> sorter) {
    RowSorter<? extends TableModel> oldRowSorter = null;
    if (sortManager != null) {
      oldRowSorter = sortManager.sorter;
      sortManager.dispose();
      sortManager = null;
    }
    rowModel = null;
    clearSelectionAndLeadAnchor();
    if (sorter != null) {
      sortManager = new SortManager(sorter);
    }
    resizeAndRepaint();
    firePropertyChange("rowSorter", oldRowSorter, sorter);
    firePropertyChange("sorter", oldRowSorter, sorter);
  }

  /**
   * Returns the object responsible for sorting.
   *
   * @return the object responsible for sorting
   * @since 1.6
   */
  public RowSorter<? extends TableModel> getRowSorter() {
    return (sortManager != null) ? sortManager.sorter : null;
  }

//
// Selection methods
//

  /**
   * Sets the table's selection mode to allow only single selections, a single
   * contiguous interval, or multiple intervals.
   * <P>
   * <b>Note:</b>
   * <code>JTable</code> provides all the methods for handling
   * column and row selection.  When setting states,
   * such as <code>setSelectionMode</code>, it not only
   * updates the mode for the row selection model but also sets similar
   * values in the selection model of the <code>columnModel</code>.
   * If you want to have the row and column selection models operating
   * in different modes, set them both directly.
   * <p>
   * Both the row and column selection models for <code>JTable</code>
   * default to using a <code>DefaultListSelectionModel</code>
   * so that <code>JTable</code> works the same way as the
   * <code>JList</code>. See the <code>setSelectionMode</code> method
   * in <code>JList</code> for details about the modes.
   *
   * @beaninfo description: The selection mode used by the row and column selection models. enum:
   * SINGLE_SELECTION            ListSelectionModel.SINGLE_SELECTION SINGLE_INTERVAL_SELECTION
   * ListSelectionModel.SINGLE_INTERVAL_SELECTION MULTIPLE_INTERVAL_SELECTION
   * ListSelectionModel.MULTIPLE_INTERVAL_SELECTION
   * @see JList#setSelectionMode
   */
  public void setSelectionMode(int selectionMode) {
    clearSelection();
    getSelectionModel().setSelectionMode(selectionMode);
    getColumnModel().getSelectionModel().setSelectionMode(selectionMode);
  }

  /**
   * Sets whether the rows in this model can be selected.
   *
   * @param rowSelectionAllowed true if this model will allow row selection
   * @beaninfo bound: true attribute: visualUpdate true description: If true, an entire row is
   * selected for each selected cell.
   * @see #getRowSelectionAllowed
   */
  public void setRowSelectionAllowed(boolean rowSelectionAllowed) {
    boolean old = this.rowSelectionAllowed;
    this.rowSelectionAllowed = rowSelectionAllowed;
    if (old != rowSelectionAllowed) {
      repaint();
    }
    firePropertyChange("rowSelectionAllowed", old, rowSelectionAllowed);
  }

  /**
   * Returns true if rows can be selected.
   *
   * @return true if rows can be selected, otherwise false
   * @see #setRowSelectionAllowed
   */
  public boolean getRowSelectionAllowed() {
    return rowSelectionAllowed;
  }

  /**
   * Sets whether the columns in this model can be selected.
   *
   * @param columnSelectionAllowed true if this model will allow column selection
   * @beaninfo bound: true attribute: visualUpdate true description: If true, an entire column is
   * selected for each selected cell.
   * @see #getColumnSelectionAllowed
   */
  public void setColumnSelectionAllowed(boolean columnSelectionAllowed) {
    boolean old = columnModel.getColumnSelectionAllowed();
    columnModel.setColumnSelectionAllowed(columnSelectionAllowed);
    if (old != columnSelectionAllowed) {
      repaint();
    }
    firePropertyChange("columnSelectionAllowed", old, columnSelectionAllowed);
  }

  /**
   * Returns true if columns can be selected.
   *
   * @return true if columns can be selected, otherwise false
   * @see #setColumnSelectionAllowed
   */
  public boolean getColumnSelectionAllowed() {
    return columnModel.getColumnSelectionAllowed();
  }

  /**
   * Sets whether this table allows both a column selection and a
   * row selection to exist simultaneously. When set,
   * the table treats the intersection of the row and column selection
   * models as the selected cells. Override <code>isCellSelected</code> to
   * change this default behavior. This method is equivalent to setting
   * both the <code>rowSelectionAllowed</code> property and
   * <code>columnSelectionAllowed</code> property of the
   * <code>columnModel</code> to the supplied value.
   *
   * @param cellSelectionEnabled true if simultaneous row and column selection is allowed
   * @beaninfo bound: true attribute: visualUpdate true description: Select a rectangular region of
   * cells rather than rows or columns.
   * @see #getCellSelectionEnabled
   * @see #isCellSelected
   */
  public void setCellSelectionEnabled(boolean cellSelectionEnabled) {
    setRowSelectionAllowed(cellSelectionEnabled);
    setColumnSelectionAllowed(cellSelectionEnabled);
    boolean old = this.cellSelectionEnabled;
    this.cellSelectionEnabled = cellSelectionEnabled;
    firePropertyChange("cellSelectionEnabled", old, cellSelectionEnabled);
  }

  /**
   * Returns true if both row and column selection models are enabled.
   * Equivalent to <code>getRowSelectionAllowed() &amp;&amp;
   * getColumnSelectionAllowed()</code>.
   *
   * @return true if both row and column selection models are enabled
   * @see #setCellSelectionEnabled
   */
  public boolean getCellSelectionEnabled() {
    return getRowSelectionAllowed() && getColumnSelectionAllowed();
  }

  /**
   * Selects all rows, columns, and cells in the table.
   */
  public void selectAll() {
    // If I'm currently editing, then I should stop editing
    if (isEditing()) {
      removeEditor();
    }
    if (getRowCount() > 0 && getColumnCount() > 0) {
      int oldLead;
      int oldAnchor;
      ListSelectionModel selModel;

      selModel = selectionModel;
      selModel.setValueIsAdjusting(true);
      oldLead = getAdjustedIndex(selModel.getLeadSelectionIndex(), true);
      oldAnchor = getAdjustedIndex(selModel.getAnchorSelectionIndex(), true);

      setRowSelectionInterval(0, getRowCount() - 1);

      // this is done to restore the anchor and lead
      SwingUtilities2.setLeadAnchorWithoutSelection(selModel, oldLead, oldAnchor);

      selModel.setValueIsAdjusting(false);

      selModel = columnModel.getSelectionModel();
      selModel.setValueIsAdjusting(true);
      oldLead = getAdjustedIndex(selModel.getLeadSelectionIndex(), false);
      oldAnchor = getAdjustedIndex(selModel.getAnchorSelectionIndex(), false);

      setColumnSelectionInterval(0, getColumnCount() - 1);

      // this is done to restore the anchor and lead
      SwingUtilities2.setLeadAnchorWithoutSelection(selModel, oldLead, oldAnchor);

      selModel.setValueIsAdjusting(false);
    }
  }

  /**
   * Deselects all selected columns and rows.
   */
  public void clearSelection() {
    selectionModel.clearSelection();
    columnModel.getSelectionModel().clearSelection();
  }

  private void clearSelectionAndLeadAnchor() {
    selectionModel.setValueIsAdjusting(true);
    columnModel.getSelectionModel().setValueIsAdjusting(true);

    clearSelection();

    selectionModel.setAnchorSelectionIndex(-1);
    selectionModel.setLeadSelectionIndex(-1);
    columnModel.getSelectionModel().setAnchorSelectionIndex(-1);
    columnModel.getSelectionModel().setLeadSelectionIndex(-1);

    selectionModel.setValueIsAdjusting(false);
    columnModel.getSelectionModel().setValueIsAdjusting(false);
  }

  private int getAdjustedIndex(int index, boolean row) {
    int compare = row ? getRowCount() : getColumnCount();
    return index < compare ? index : -1;
  }

  private int boundRow(int row) throws IllegalArgumentException {
    if (row < 0 || row >= getRowCount()) {
      throw new IllegalArgumentException("Row index out of range");
    }
    return row;
  }

  private int boundColumn(int col) {
    if (col < 0 || col >= getColumnCount()) {
      throw new IllegalArgumentException("Column index out of range");
    }
    return col;
  }

  /**
   * Selects the rows from <code>index0</code> to <code>index1</code>,
   * inclusive.
   *
   * @param index0 one end of the interval
   * @param index1 the other end of the interval
   * @throws IllegalArgumentException if <code>index0</code> or <code>index1</code> lie outside [0,
   * <code>getRowCount()</code>-1]
   */
  public void setRowSelectionInterval(int index0, int index1) {
    selectionModel.setSelectionInterval(boundRow(index0), boundRow(index1));
  }

  /**
   * Selects the columns from <code>index0</code> to <code>index1</code>,
   * inclusive.
   *
   * @param index0 one end of the interval
   * @param index1 the other end of the interval
   * @throws IllegalArgumentException if <code>index0</code> or <code>index1</code> lie outside [0,
   * <code>getColumnCount()</code>-1]
   */
  public void setColumnSelectionInterval(int index0, int index1) {
    columnModel.getSelectionModel().setSelectionInterval(boundColumn(index0), boundColumn(index1));
  }

  /**
   * Adds the rows from <code>index0</code> to <code>index1</code>, inclusive, to
   * the current selection.
   *
   * @param index0 one end of the interval
   * @param index1 the other end of the interval
   * @throws IllegalArgumentException if <code>index0</code> or <code>index1</code> lie outside [0,
   * <code>getRowCount()</code>-1]
   */
  public void addRowSelectionInterval(int index0, int index1) {
    selectionModel.addSelectionInterval(boundRow(index0), boundRow(index1));
  }

  /**
   * Adds the columns from <code>index0</code> to <code>index1</code>,
   * inclusive, to the current selection.
   *
   * @param index0 one end of the interval
   * @param index1 the other end of the interval
   * @throws IllegalArgumentException if <code>index0</code> or <code>index1</code> lie outside [0,
   * <code>getColumnCount()</code>-1]
   */
  public void addColumnSelectionInterval(int index0, int index1) {
    columnModel.getSelectionModel().addSelectionInterval(boundColumn(index0), boundColumn(index1));
  }

  /**
   * Deselects the rows from <code>index0</code> to <code>index1</code>, inclusive.
   *
   * @param index0 one end of the interval
   * @param index1 the other end of the interval
   * @throws IllegalArgumentException if <code>index0</code> or <code>index1</code> lie outside [0,
   * <code>getRowCount()</code>-1]
   */
  public void removeRowSelectionInterval(int index0, int index1) {
    selectionModel.removeSelectionInterval(boundRow(index0), boundRow(index1));
  }

  /**
   * Deselects the columns from <code>index0</code> to <code>index1</code>, inclusive.
   *
   * @param index0 one end of the interval
   * @param index1 the other end of the interval
   * @throws IllegalArgumentException if <code>index0</code> or <code>index1</code> lie outside [0,
   * <code>getColumnCount()</code>-1]
   */
  public void removeColumnSelectionInterval(int index0, int index1) {
    columnModel.getSelectionModel()
        .removeSelectionInterval(boundColumn(index0), boundColumn(index1));
  }

  /**
   * Returns the index of the first selected row, -1 if no row is selected.
   *
   * @return the index of the first selected row
   */
  public int getSelectedRow() {
    return selectionModel.getMinSelectionIndex();
  }

  /**
   * Returns the index of the first selected column,
   * -1 if no column is selected.
   *
   * @return the index of the first selected column
   */
  public int getSelectedColumn() {
    return columnModel.getSelectionModel().getMinSelectionIndex();
  }

  /**
   * Returns the indices of all selected rows.
   *
   * @return an array of integers containing the indices of all selected rows, or an empty array if
   * no row is selected
   * @see #getSelectedRow
   */
  public int[] getSelectedRows() {
    int iMin = selectionModel.getMinSelectionIndex();
    int iMax = selectionModel.getMaxSelectionIndex();

    if ((iMin == -1) || (iMax == -1)) {
      return new int[0];
    }

    int[] rvTmp = new int[1 + (iMax - iMin)];
    int n = 0;
    for (int i = iMin; i <= iMax; i++) {
      if (selectionModel.isSelectedIndex(i)) {
        rvTmp[n++] = i;
      }
    }
    int[] rv = new int[n];
    System.arraycopy(rvTmp, 0, rv, 0, n);
    return rv;
  }

  /**
   * Returns the indices of all selected columns.
   *
   * @return an array of integers containing the indices of all selected columns, or an empty array
   * if no column is selected
   * @see #getSelectedColumn
   */
  public int[] getSelectedColumns() {
    return columnModel.getSelectedColumns();
  }

  /**
   * Returns the number of selected rows.
   *
   * @return the number of selected rows, 0 if no rows are selected
   */
  public int getSelectedRowCount() {
    int iMin = selectionModel.getMinSelectionIndex();
    int iMax = selectionModel.getMaxSelectionIndex();
    int count = 0;

    for (int i = iMin; i <= iMax; i++) {
      if (selectionModel.isSelectedIndex(i)) {
        count++;
      }
    }
    return count;
  }

  /**
   * Returns the number of selected columns.
   *
   * @return the number of selected columns, 0 if no columns are selected
   */
  public int getSelectedColumnCount() {
    return columnModel.getSelectedColumnCount();
  }

  /**
   * Returns true if the specified index is in the valid range of rows,
   * and the row at that index is selected.
   *
   * @return true if <code>row</code> is a valid index and the row at that index is selected (where
   * 0 is the first row)
   */
  public boolean isRowSelected(int row) {
    return selectionModel.isSelectedIndex(row);
  }

  /**
   * Returns true if the specified index is in the valid range of columns,
   * and the column at that index is selected.
   *
   * @param column the column in the column model
   * @return true if <code>column</code> is a valid index and the column at that index is selected
   * (where 0 is the first column)
   */
  public boolean isColumnSelected(int column) {
    return columnModel.getSelectionModel().isSelectedIndex(column);
  }

  /**
   * Returns true if the specified indices are in the valid range of rows
   * and columns and the cell at the specified position is selected.
   *
   * @param row the row being queried
   * @param column the column being queried
   * @return true if <code>row</code> and <code>column</code> are valid indices and the cell at
   * index <code>(row, column)</code> is selected, where the first row and first column are at index
   * 0
   */
  public boolean isCellSelected(int row, int column) {
    if (!getRowSelectionAllowed() && !getColumnSelectionAllowed()) {
      return false;
    }
    return (!getRowSelectionAllowed() || isRowSelected(row)) &&
        (!getColumnSelectionAllowed() || isColumnSelected(column));
  }

  private void changeSelectionModel(ListSelectionModel sm, int index,
      boolean toggle, boolean extend, boolean selected,
      int anchor, boolean anchorSelected) {
    if (extend) {
      if (toggle) {
        if (anchorSelected) {
          sm.addSelectionInterval(anchor, index);
        } else {
          sm.removeSelectionInterval(anchor, index);
          // this is a Windows-only behavior that we want for file lists
          if (Boolean.TRUE == getClientProperty("Table.isFileList")) {
            sm.addSelectionInterval(index, index);
            sm.setAnchorSelectionIndex(anchor);
          }
        }
      } else {
        sm.setSelectionInterval(anchor, index);
      }
    } else {
      if (toggle) {
        if (selected) {
          sm.removeSelectionInterval(index, index);
        } else {
          sm.addSelectionInterval(index, index);
        }
      } else {
        sm.setSelectionInterval(index, index);
      }
    }
  }

  /**
   * Updates the selection models of the table, depending on the state of the
   * two flags: <code>toggle</code> and <code>extend</code>. Most changes
   * to the selection that are the result of keyboard or mouse events received
   * by the UI are channeled through this method so that the behavior may be
   * overridden by a subclass. Some UIs may need more functionality than
   * this method provides, such as when manipulating the lead for discontiguous
   * selection, and may not call into this method for some selection changes.
   * <p>
   * This implementation uses the following conventions:
   * <ul>
   * <li> <code>toggle</code>: <em>false</em>, <code>extend</code>: <em>false</em>.
   * Clear the previous selection and ensure the new cell is selected.
   * <li> <code>toggle</code>: <em>false</em>, <code>extend</code>: <em>true</em>.
   * Extend the previous selection from the anchor to the specified cell,
   * clearing all other selections.
   * <li> <code>toggle</code>: <em>true</em>, <code>extend</code>: <em>false</em>.
   * If the specified cell is selected, deselect it. If it is not selected, select it.
   * <li> <code>toggle</code>: <em>true</em>, <code>extend</code>: <em>true</em>.
   * Apply the selection state of the anchor to all cells between it and the
   * specified cell.
   * </ul>
   *
   * @param rowIndex affects the selection at <code>row</code>
   * @param columnIndex affects the selection at <code>column</code>
   * @param toggle see description above
   * @param extend if true, extend the current selection
   * @since 1.3
   */
  public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) {
    ListSelectionModel rsm = getSelectionModel();
    ListSelectionModel csm = getColumnModel().getSelectionModel();

    int anchorRow = getAdjustedIndex(rsm.getAnchorSelectionIndex(), true);
    int anchorCol = getAdjustedIndex(csm.getAnchorSelectionIndex(), false);

    boolean anchorSelected = true;

    if (anchorRow == -1) {
      if (getRowCount() > 0) {
        anchorRow = 0;
      }
      anchorSelected = false;
    }

    if (anchorCol == -1) {
      if (getColumnCount() > 0) {
        anchorCol = 0;
      }
      anchorSelected = false;
    }

    // Check the selection here rather than in each selection model.
    // This is significant in cell selection mode if we are supposed
    // to be toggling the selection. In this case it is better to
    // ensure that the cell's selection state will indeed be changed.
    // If this were done in the code for the selection model it
    // might leave a cell in selection state if the row was
    // selected but the column was not - as it would toggle them both.
    boolean selected = isCellSelected(rowIndex, columnIndex);
    anchorSelected = anchorSelected && isCellSelected(anchorRow, anchorCol);

    changeSelectionModel(csm, columnIndex, toggle, extend, selected,
        anchorCol, anchorSelected);
    changeSelectionModel(rsm, rowIndex, toggle, extend, selected,
        anchorRow, anchorSelected);

    // Scroll after changing the selection as blit scrolling is immediate,
    // so that if we cause the repaint after the scroll we end up painting
    // everything!
    if (getAutoscrolls()) {
      Rectangle cellRect = getCellRect(rowIndex, columnIndex, false);
      if (cellRect != null) {
        scrollRectToVisible(cellRect);
      }
    }
  }

  /**
   * Returns the foreground color for selected cells.
   *
   * @return the <code>Color</code> object for the foreground property
   * @see #setSelectionForeground
   * @see #setSelectionBackground
   */
  public Color getSelectionForeground() {
    return selectionForeground;
  }

  /**
   * Sets the foreground color for selected cells.  Cell renderers can use this color to render text
   * and graphics for selected cells. <p> The default value of this property is defined by the look
   * and feel implementation. <p> This is a <a href="https://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html">JavaBeans</a>
   * bound property.
   *
   * @param selectionForeground the <code>Color</code> to use in the foreground for selected list
   * items
   * @beaninfo bound: true description: A default foreground color for selected cells.
   * @see #getSelectionForeground
   * @see #setSelectionBackground
   * @see #setForeground
   * @see #setBackground
   * @see #setFont
   */
  public void setSelectionForeground(Color selectionForeground) {
    Color old = this.selectionForeground;
    this.selectionForeground = selectionForeground;
    firePropertyChange("selectionForeground", old, selectionForeground);
    repaint();
  }

  /**
   * Returns the background color for selected cells.
   *
   * @return the <code>Color</code> used for the background of selected list items
   * @see #setSelectionBackground
   * @see #setSelectionForeground
   */
  public Color getSelectionBackground() {
    return selectionBackground;
  }

  /**
   * Sets the background color for selected cells.  Cell renderers can use this color to the fill
   * selected cells. <p> The default value of this property is defined by the look and feel
   * implementation. <p> This is a <a href="https://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html">JavaBeans</a>
   * bound property.
   *
   * @param selectionBackground the <code>Color</code> to use for the background of selected cells
   * @beaninfo bound: true description: A default background color for selected cells.
   * @see #getSelectionBackground
   * @see #setSelectionForeground
   * @see #setForeground
   * @see #setBackground
   * @see #setFont
   */
  public void setSelectionBackground(Color selectionBackground) {
    Color old = this.selectionBackground;
    this.selectionBackground = selectionBackground;
    firePropertyChange("selectionBackground", old, selectionBackground);
    repaint();
  }

  /**
   * Returns the <code>TableColumn</code> object for the column in the table
   * whose identifier is equal to <code>identifier</code>, when compared using
   * <code>equals</code>.
   *
   * @param identifier the identifier object
   * @return the <code>TableColumn</code> object that matches the identifier
   * @throws IllegalArgumentException if <code>identifier</code> is <code>null</code> or no
   * <code>TableColumn</code> has this identifier
   */
  public TableColumn getColumn(Object identifier) {
    TableColumnModel cm = getColumnModel();
    int columnIndex = cm.getColumnIndex(identifier);
    return cm.getColumn(columnIndex);
  }

//
// Informally implement the TableModel interface.
//

  /**
   * Maps the index of the column in the view at
   * <code>viewColumnIndex</code> to the index of the column
   * in the table model.  Returns the index of the corresponding
   * column in the model.  If <code>viewColumnIndex</code>
   * is less than zero, returns <code>viewColumnIndex</code>.
   *
   * @param viewColumnIndex the index of the column in the view
   * @return the index of the corresponding column in the model
   * @see #convertColumnIndexToView
   */
  public int convertColumnIndexToModel(int viewColumnIndex) {
    return SwingUtilities2.convertColumnIndexToModel(
        getColumnModel(), viewColumnIndex);
  }

  /**
   * Maps the index of the column in the table model at
   * <code>modelColumnIndex</code> to the index of the column
   * in the view.  Returns the index of the
   * corresponding column in the view; returns -1 if this column is not
   * being displayed.  If <code>modelColumnIndex</code> is less than zero,
   * returns <code>modelColumnIndex</code>.
   *
   * @param modelColumnIndex the index of the column in the model
   * @return the index of the corresponding column in the view
   * @see #convertColumnIndexToModel
   */
  public int convertColumnIndexToView(int modelColumnIndex) {
    return SwingUtilities2.convertColumnIndexToView(
        getColumnModel(), modelColumnIndex);
  }

  /**
   * Maps the index of the row in terms of the
   * <code>TableModel</code> to the view.  If the contents of the
   * model are not sorted the model and view indices are the same.
   *
   * @param modelRowIndex the index of the row in terms of the model
   * @return the index of the corresponding row in the view, or -1 if the row isn't visible
   * @throws IndexOutOfBoundsException if sorting is enabled and passed an index outside the number
   * of rows of the <code>TableModel</code>
   * @see javax.swing.table.TableRowSorter
   * @since 1.6
   */
  public int convertRowIndexToView(int modelRowIndex) {
    RowSorter sorter = getRowSorter();
    if (sorter != null) {
      return sorter.convertRowIndexToView(modelRowIndex);
    }
    return modelRowIndex;
  }

  /**
   * Maps the index of the row in terms of the view to the
   * underlying <code>TableModel</code>.  If the contents of the
   * model are not sorted the model and view indices are the same.
   *
   * @param viewRowIndex the index of the row in the view
   * @return the index of the corresponding row in the model
   * @throws IndexOutOfBoundsException if sorting is enabled and passed an index outside the range
   * of the <code>JTable</code> as determined by the method <code>getRowCount</code>
   * @see javax.swing.table.TableRowSorter
   * @see #getRowCount
   * @since 1.6
   */
  public int convertRowIndexToModel(int viewRowIndex) {
    RowSorter sorter = getRowSorter();
    if (sorter != null) {
      return sorter.convertRowIndexToModel(viewRowIndex);
    }
    return viewRowIndex;
  }

  /**
   * Returns the number of rows that can be shown in the
   * <code>JTable</code>, given unlimited space.  If a
   * <code>RowSorter</code> with a filter has been specified, the
   * number of rows returned may differ from that of the underlying
   * <code>TableModel</code>.
   *
   * @return the number of rows shown in the <code>JTable</code>
   * @see #getColumnCount
   */
  public int getRowCount() {
    RowSorter sorter = getRowSorter();
    if (sorter != null) {
      return sorter.getViewRowCount();
    }
    return getModel().getRowCount();
  }

  /**
   * Returns the number of columns in the column model. Note that this may
   * be different from the number of columns in the table model.
   *
   * @return the number of columns in the table
   * @see #getRowCount
   * @see #removeColumn
   */
  public int getColumnCount() {
    return getColumnModel().getColumnCount();
  }

  /**
   * Returns the name of the column appearing in the view at
   * column position <code>column</code>.
   *
   * @param column the column in the view being queried
   * @return the name of the column at position <code>column</code> in the view where the first
   * column is column 0
   */
  public String getColumnName(int column) {
    return getModel().getColumnName(convertColumnIndexToModel(column));
  }

  /**
   * Returns the type of the column appearing in the view at
   * column position <code>column</code>.
   *
   * @param column the column in the view being queried
   * @return the type of the column at position <code>column</code> in the view where the first
   * column is column 0
   */
  public Class<?> getColumnClass(int column) {
    return getModel().getColumnClass(convertColumnIndexToModel(column));
  }

  /**
   * Returns the cell value at <code>row</code> and <code>column</code>.
   * <p>
   * <b>Note</b>: The column is specified in the table view's display
   * order, and not in the <code>TableModel</code>'s column
   * order.  This is an important distinction because as the
   * user rearranges the columns in the table,
   * the column at a given index in the view will change.
   * Meanwhile the user's actions never affect the model's
   * column ordering.
   *
   * @param row the row whose value is to be queried
   * @param column the column whose value is to be queried
   * @return the Object at the specified cell
   */
  public Object getValueAt(int row, int column) {
    return getModel().getValueAt(convertRowIndexToModel(row),
        convertColumnIndexToModel(column));
  }

  /**
   * Sets the value for the cell in the table model at <code>row</code>
   * and <code>column</code>.
   * <p>
   * <b>Note</b>: The column is specified in the table view's display
   * order, and not in the <code>TableModel</code>'s column
   * order.  This is an important distinction because as the
   * user rearranges the columns in the table,
   * the column at a given index in the view will change.
   * Meanwhile the user's actions never affect the model's
   * column ordering.
   *
   * <code>aValue</code> is the new value.
   *
   * @param aValue the new value
   * @param row the row of the cell to be changed
   * @param column the column of the cell to be changed
   * @see #getValueAt
   */
  public void setValueAt(Object aValue, int row, int column) {
    getModel().setValueAt(aValue, convertRowIndexToModel(row),
        convertColumnIndexToModel(column));
  }

  /**
   * Returns true if the cell at <code>row</code> and <code>column</code>
   * is editable.  Otherwise, invoking <code>setValueAt</code> on the cell
   * will have no effect.
   * <p>
   * <b>Note</b>: The column is specified in the table view's display
   * order, and not in the <code>TableModel</code>'s column
   * order.  This is an important distinction because as the
   * user rearranges the columns in the table,
   * the column at a given index in the view will change.
   * Meanwhile the user's actions never affect the model's
   * column ordering.
   *
   * @param row the row whose value is to be queried
   * @param column the column whose value is to be queried
   * @return true if the cell is editable
   * @see #setValueAt
   */
  public boolean isCellEditable(int row, int column) {
    return getModel().isCellEditable(convertRowIndexToModel(row),
        convertColumnIndexToModel(column));
  }
//
// Adding and removing columns in the view
//

  /**
   * Appends <code>aColumn</code> to the end of the array of columns held by
   * this <code>JTable</code>'s column model.
   * If the column name of <code>aColumn</code> is <code>null</code>,
   * sets the column name of <code>aColumn</code> to the name
   * returned by <code>getModel().getColumnName()</code>.
   * <p>
   * To add a column to this <code>JTable</code> to display the
   * <code>modelColumn</code>'th column of data in the model with a
   * given <code>width</code>, <code>cellRenderer</code>,
   * and <code>cellEditor</code> you can use:
   * <pre>
   *
   *      addColumn(new TableColumn(modelColumn, width, cellRenderer, cellEditor));
   *
   *  </pre>
   * [Any of the <code>TableColumn</code> constructors can be used
   * instead of this one.]
   * The model column number is stored inside the <code>TableColumn</code>
   * and is used during rendering and editing to locate the appropriates
   * data values in the model. The model column number does not change
   * when columns are reordered in the view.
   *
   * @param aColumn the <code>TableColumn</code> to be added
   * @see #removeColumn
   */
  public void addColumn(TableColumn aColumn) {
    if (aColumn.getHeaderValue() == null) {
      int modelColumn = aColumn.getModelIndex();
      String columnName = getModel().getColumnName(modelColumn);
      aColumn.setHeaderValue(columnName);
    }
    getColumnModel().addColumn(aColumn);
  }

  /**
   * Removes <code>aColumn</code> from this <code>JTable</code>'s
   * array of columns.  Note: this method does not remove the column
   * of data from the model; it just removes the <code>TableColumn</code>
   * that was responsible for displaying it.
   *
   * @param aColumn the <code>TableColumn</code> to be removed
   * @see #addColumn
   */
  public void removeColumn(TableColumn aColumn) {
    getColumnModel().removeColumn(aColumn);
  }

  /**
   * Moves the column <code>column</code> to the position currently
   * occupied by the column <code>targetColumn</code> in the view.
   * The old column at <code>targetColumn</code> is
   * shifted left or right to make room.
   *
   * @param column the index of column to be moved
   * @param targetColumn the new index of the column
   */
  public void moveColumn(int column, int targetColumn) {
    getColumnModel().moveColumn(column, targetColumn);
  }

//
// Cover methods for various models and helper methods
//

  /**
   * Returns the index of the column that <code>point</code> lies in,
   * or -1 if the result is not in the range
   * [0, <code>getColumnCount()</code>-1].
   *
   * @param point the location of interest
   * @return the index of the column that <code>point</code> lies in, or -1 if the result is not in
   * the range [0, <code>getColumnCount()</code>-1]
   * @see #rowAtPoint
   */
  public int columnAtPoint(Point point) {
    int x = point.x;
    if (!getComponentOrientation().isLeftToRight()) {
      x = getWidth() - x - 1;
    }
    return getColumnModel().getColumnIndexAtX(x);
  }

  /**
   * Returns the index of the row that <code>point</code> lies in,
   * or -1 if the result is not in the range
   * [0, <code>getRowCount()</code>-1].
   *
   * @param point the location of interest
   * @return the index of the row that <code>point</code> lies in, or -1 if the result is not in the
   * range [0, <code>getRowCount()</code>-1]
   * @see #columnAtPoint
   */
  public int rowAtPoint(Point point) {
    int y = point.y;
    int result = (rowModel == null) ? y / getRowHeight() : rowModel.getIndex(y);
    if (result < 0) {
      return -1;
    } else if (result >= getRowCount()) {
      return -1;
    } else {
      return result;
    }
  }

  /**
   * Returns a rectangle for the cell that lies at the intersection of
   * <code>row</code> and <code>column</code>.
   * If <code>includeSpacing</code> is true then the value returned
   * has the full height and width of the row and column
   * specified. If it is false, the returned rectangle is inset by the
   * intercell spacing to return the true bounds of the rendering or
   * editing component as it will be set during rendering.
   * <p>
   * If the column index is valid but the row index is less
   * than zero the method returns a rectangle with the
   * <code>y</code> and <code>height</code> values set appropriately
   * and the <code>x</code> and <code>width</code> values both set
   * to zero. In general, when either the row or column indices indicate a
   * cell outside the appropriate range, the method returns a rectangle
   * depicting the closest edge of the closest cell that is within
   * the table's range. When both row and column indices are out
   * of range the returned rectangle covers the closest
   * point of the closest cell.
   * <p>
   * In all cases, calculations that use this method to calculate
   * results along one axis will not fail because of anomalies in
   * calculations along the other axis. When the cell is not valid
   * the <code>includeSpacing</code> parameter is ignored.
   *
   * @param row the row index where the desired cell is located
   * @param column the column index where the desired cell is located in the display; this is not
   * necessarily the same as the column index in the data model for the table; the {@link
   * #convertColumnIndexToView(int)} method may be used to convert a data model column index to a
   * display column index
   * @param includeSpacing if false, return the true cell bounds - computed by subtracting the
   * intercell spacing from the height and widths of the column and row models
   * @return the rectangle containing the cell at location <code>row</code>,<code>column</code>
   * @see #getIntercellSpacing
   */
  public Rectangle getCellRect(int row, int column, boolean includeSpacing) {
    Rectangle r = new Rectangle();
    boolean valid = true;
    if (row < 0) {
      // y = height = 0;
      valid = false;
    } else if (row >= getRowCount()) {
      r.y = getHeight();
      valid = false;
    } else {
      r.height = getRowHeight(row);
      r.y = (rowModel == null) ? row * r.height : rowModel.getPosition(row);
    }

    if (column < 0) {
      if (!getComponentOrientation().isLeftToRight()) {
        r.x = getWidth();
      }
      // otherwise, x = width = 0;
      valid = false;
    } else if (column >= getColumnCount()) {
      if (getComponentOrientation().isLeftToRight()) {
        r.x = getWidth();
      }
      // otherwise, x = width = 0;
      valid = false;
    } else {
      TableColumnModel cm = getColumnModel();
      if (getComponentOrientation().isLeftToRight()) {
        for (int i = 0; i < column; i++) {
          r.x += cm.getColumn(i).getWidth();
        }
      } else {
        for (int i = cm.getColumnCount() - 1; i > column; i--) {
          r.x += cm.getColumn(i).getWidth();
        }
      }
      r.width = cm.getColumn(column).getWidth();
    }

    if (valid && !includeSpacing) {
      // Bound the margins by their associated dimensions to prevent
      // returning bounds with negative dimensions.
      int rm = Math.min(getRowMargin(), r.height);
      int cm = Math.min(getColumnModel().getColumnMargin(), r.width);
      // This is not the same as grow(), it rounds differently.
      r.setBounds(r.x + cm / 2, r.y + rm / 2, r.width - cm, r.height - rm);
    }
    return r;
  }

  private int viewIndexForColumn(TableColumn aColumn) {
    TableColumnModel cm = getColumnModel();
    for (int column = 0; column < cm.getColumnCount(); column++) {
      if (cm.getColumn(column) == aColumn) {
        return column;
      }
    }
    return -1;
  }

  /**
   * Causes this table to lay out its rows and columns.  Overridden so
   * that columns can be resized to accommodate a change in the size of
   * a containing parent.
   * Resizes one or more of the columns in the table
   * so that the total width of all of this <code>JTable</code>'s
   * columns is equal to the width of the table.
   * <p>
   * Before the layout begins the method gets the
   * <code>resizingColumn</code> of the <code>tableHeader</code>.
   * When the method is called as a result of the resizing of an enclosing window,
   * the <code>resizingColumn</code> is <code>null</code>. This means that resizing
   * has taken place "outside" the <code>JTable</code> and the change -
   * or "delta" - should be distributed to all of the columns regardless
   * of this <code>JTable</code>'s automatic resize mode.
   * <p>
   * If the <code>resizingColumn</code> is not <code>null</code>, it is one of
   * the columns in the table that has changed size rather than
   * the table itself. In this case the auto-resize modes govern
   * the way the extra (or deficit) space is distributed
   * amongst the available columns.
   * <p>
   * The modes are:
   * <ul>
   * <li>  AUTO_RESIZE_OFF: Don't automatically adjust the column's
   * widths at all. Use a horizontal scrollbar to accommodate the
   * columns when their sum exceeds the width of the
   * <code>Viewport</code>.  If the <code>JTable</code> is not
   * enclosed in a <code>JScrollPane</code> this may
   * leave parts of the table invisible.
   * <li>  AUTO_RESIZE_NEXT_COLUMN: Use just the column after the
   * resizing column. This results in the "boundary" or divider
   * between adjacent cells being independently adjustable.
   * <li>  AUTO_RESIZE_SUBSEQUENT_COLUMNS: Use all columns after the
   * one being adjusted to absorb the changes.  This is the
   * default behavior.
   * <li>  AUTO_RESIZE_LAST_COLUMN: Automatically adjust the
   * size of the last column only. If the bounds of the last column
   * prevent the desired size from being allocated, set the
   * width of the last column to the appropriate limit and make
   * no further adjustments.
   * <li>  AUTO_RESIZE_ALL_COLUMNS: Spread the delta amongst all the columns
   * in the <code>JTable</code>, including the one that is being
   * adjusted.
   * </ul>
   * <p>
   * <b>Note:</b> When a <code>JTable</code> makes adjustments
   * to the widths of the columns it respects their minimum and
   * maximum values absolutely.  It is therefore possible that,
   * even after this method is called, the total width of the columns
   * is still not equal to the width of the table. When this happens
   * the <code>JTable</code> does not put itself
   * in AUTO_RESIZE_OFF mode to bring up a scroll bar, or break other
   * commitments of its current auto-resize mode -- instead it
   * allows its bounds to be set larger (or smaller) than the total of the
   * column minimum or maximum, meaning, either that there
   * will not be enough room to display all of the columns, or that the
   * columns will not fill the <code>JTable</code>'s bounds.
   * These respectively, result in the clipping of some columns
   * or an area being painted in the <code>JTable</code>'s
   * background color during painting.
   * <p>
   * The mechanism for distributing the delta amongst the available
   * columns is provided in a private method in the <code>JTable</code>
   * class:
   * <pre>
   *   adjustSizes(long targetSize, final Resizable3 r, boolean inverse)
   * </pre>
   * an explanation of which is provided in the following section.
   * <code>Resizable3</code> is a private
   * interface that allows any data structure containing a collection
   * of elements with a size, preferred size, maximum size and minimum size
   * to have its elements manipulated by the algorithm.
   *
   * <H3> Distributing the delta </H3>
   *
   * <H4> Overview </H4>
   * <P>
   * Call "DELTA" the difference between the target size and the
   * sum of the preferred sizes of the elements in r. The individual
   * sizes are calculated by taking the original preferred
   * sizes and adding a share of the DELTA - that share being based on
   * how far each preferred size is from its limiting bound (minimum or
   * maximum).
   *
   * <H4>Definition</H4>
   * <P>
   * Call the individual constraints min[i], max[i], and pref[i].
   * <p>
   * Call their respective sums: MIN, MAX, and PREF.
   * <p>
   * Each new size will be calculated using:
   *
   * <pre>
   *          size[i] = pref[i] + delta[i]
   * </pre>
   * where each individual delta[i] is calculated according to:
   * <p>
   * If (DELTA &lt; 0) we are in shrink mode where:
   *
   * <PRE>
   * DELTA
   * delta[i] = ------------ * (pref[i] - min[i])
   * (PREF - MIN)
   * </PRE>
   * If (DELTA &gt; 0) we are in expand mode where:
   *
   * <PRE>
   * DELTA
   * delta[i] = ------------ * (max[i] - pref[i])
   * (MAX - PREF)
   * </PRE>
   * <P>
   * The overall effect is that the total size moves that same percentage,
   * k, towards the total minimum or maximum and that percentage guarantees
   * accommodation of the required space, DELTA.
   *
   * <H4>Details</H4>
   * <P>
   * Naive evaluation of the formulae presented here would be subject to
   * the aggregated rounding errors caused by doing this operation in finite
   * precision (using ints). To deal with this, the multiplying factor above,
   * is constantly recalculated and this takes account of the rounding
   * errors in the previous iterations. The result is an algorithm that
   * produces a set of integers whose values exactly sum to the supplied
   * <code>targetSize</code>, and does so by spreading the rounding
   * errors evenly over the given elements.
   *
   * <H4>When the MAX and MIN bounds are hit</H4>
   * <P>
   * When <code>targetSize</code> is outside the [MIN, MAX] range,
   * the algorithm sets all sizes to their appropriate limiting value
   * (maximum or minimum).
   */
  public void doLayout() {
    TableColumn resizingColumn = getResizingColumn();
    if (resizingColumn == null) {
      setWidthsFromPreferredWidths(false);
    } else {
      // JTable behaves like a layout manger - but one in which the
      // user can come along and dictate how big one of the children
      // (columns) is supposed to be.

      // A column has been resized and JTable may need to distribute
      // any overall delta to other columns, according to the resize mode.
      int columnIndex = viewIndexForColumn(resizingColumn);
      int delta = getWidth() - getColumnModel().getTotalColumnWidth();
      accommodateDelta(columnIndex, delta);
      delta = getWidth() - getColumnModel().getTotalColumnWidth();

      // If the delta cannot be completely accomodated, then the
      // resizing column will have to take any remainder. This means
      // that the column is not being allowed to take the requested
      // width. This happens under many circumstances: For example,
      // AUTO_RESIZE_NEXT_COLUMN specifies that any delta be distributed
      // to the column after the resizing column. If one were to attempt
      // to resize the last column of the table, there would be no
      // columns after it, and hence nowhere to distribute the delta.
      // It would then be given entirely back to the resizing column,
      // preventing it from changing size.
      if (delta != 0) {
        resizingColumn.setWidth(resizingColumn.getWidth() + delta);
      }

      // At this point the JTable has to work out what preferred sizes
      // would have resulted in the layout the user has chosen.
      // Thereafter, during window resizing etc. it has to work off
      // the preferred sizes as usual - the idea being that, whatever
      // the user does, everything stays in synch and things don't jump
      // around.
      setWidthsFromPreferredWidths(true);
    }

    super.doLayout();
  }

  private TableColumn getResizingColumn() {
    return (tableHeader == null) ? null
        : tableHeader.getResizingColumn();
  }

  /**
   * Sizes the table columns to fit the available space.
   *
   * @see #doLayout
   * @deprecated As of Swing version 1.0.3, replaced by <code>doLayout()</code>.
   */
  @Deprecated
  public void sizeColumnsToFit(boolean lastColumnOnly) {
    int oldAutoResizeMode = autoResizeMode;
    setAutoResizeMode(lastColumnOnly ? AUTO_RESIZE_LAST_COLUMN
        : AUTO_RESIZE_ALL_COLUMNS);
    sizeColumnsToFit(-1);
    setAutoResizeMode(oldAutoResizeMode);
  }

  /**
   * Obsolete as of Java 2 platform v1.4.  Please use the
   * <code>doLayout()</code> method instead.
   *
   * @param resizingColumn the column whose resizing made this adjustment necessary or -1 if there
   * is no such column
   * @see #doLayout
   */
  public void sizeColumnsToFit(int resizingColumn) {
    if (resizingColumn == -1) {
      setWidthsFromPreferredWidths(false);
    } else {
      if (autoResizeMode == AUTO_RESIZE_OFF) {
        TableColumn aColumn = getColumnModel().getColumn(resizingColumn);
        aColumn.setPreferredWidth(aColumn.getWidth());
      } else {
        int delta = getWidth() - getColumnModel().getTotalColumnWidth();
        accommodateDelta(resizingColumn, delta);
        setWidthsFromPreferredWidths(true);
      }
    }
  }

  private void setWidthsFromPreferredWidths(final boolean inverse) {
    int totalWidth = getWidth();
    int totalPreferred = getPreferredSize().width;
    int target = !inverse ? totalWidth : totalPreferred;

    final TableColumnModel cm = columnModel;
    Resizable3 r = new Resizable3() {
      public int getElementCount() {
        return cm.getColumnCount();
      }

      public int getLowerBoundAt(int i) {
        return cm.getColumn(i).getMinWidth();
      }

      public int getUpperBoundAt(int i) {
        return cm.getColumn(i).getMaxWidth();
      }

      public int getMidPointAt(int i) {
        if (!inverse) {
          return cm.getColumn(i).getPreferredWidth();
        } else {
          return cm.getColumn(i).getWidth();
        }
      }

      public void setSizeAt(int s, int i) {
        if (!inverse) {
          cm.getColumn(i).setWidth(s);
        } else {
          cm.getColumn(i).setPreferredWidth(s);
        }
      }
    };

    adjustSizes(target, r, inverse);
  }


  // Distribute delta over columns, as indicated by the autoresize mode.
  private void accommodateDelta(int resizingColumnIndex, int delta) {
    int columnCount = getColumnCount();
    int from = resizingColumnIndex;
    int to;

    // Use the mode to determine how to absorb the changes.
    switch (autoResizeMode) {
      case AUTO_RESIZE_NEXT_COLUMN:
        from = from + 1;
        to = Math.min(from + 1, columnCount);
        break;
      case AUTO_RESIZE_SUBSEQUENT_COLUMNS:
        from = from + 1;
        to = columnCount;
        break;
      case AUTO_RESIZE_LAST_COLUMN:
        from = columnCount - 1;
        to = from + 1;
        break;
      case AUTO_RESIZE_ALL_COLUMNS:
        from = 0;
        to = columnCount;
        break;
      default:
        return;
    }

    final int start = from;
    final int end = to;
    final TableColumnModel cm = columnModel;
    Resizable3 r = new Resizable3() {
      public int getElementCount() {
        return end - start;
      }

      public int getLowerBoundAt(int i) {
        return cm.getColumn(i + start).getMinWidth();
      }

      public int getUpperBoundAt(int i) {
        return cm.getColumn(i + start).getMaxWidth();
      }

      public int getMidPointAt(int i) {
        return cm.getColumn(i + start).getWidth();
      }

      public void setSizeAt(int s, int i) {
        cm.getColumn(i + start).setWidth(s);
      }
    };

    int totalWidth = 0;
    for (int i = from; i < to; i++) {
      TableColumn aColumn = columnModel.getColumn(i);
      int input = aColumn.getWidth();
      totalWidth = totalWidth + input;
    }

    adjustSizes(totalWidth + delta, r, false);
  }

  private interface Resizable2 {

    public int getElementCount();

    public int getLowerBoundAt(int i);

    public int getUpperBoundAt(int i);

    public void setSizeAt(int newSize, int i);
  }

  private interface Resizable3 extends Resizable2 {

    public int getMidPointAt(int i);
  }


  private void adjustSizes(long target, final Resizable3 r, boolean inverse) {
    int N = r.getElementCount();
    long totalPreferred = 0;
    for (int i = 0; i < N; i++) {
      totalPreferred += r.getMidPointAt(i);
    }
    Resizable2 s;
    if ((target < totalPreferred) == !inverse) {
      s = new Resizable2() {
        public int getElementCount() {
          return r.getElementCount();
        }

        public int getLowerBoundAt(int i) {
          return r.getLowerBoundAt(i);
        }

        public int getUpperBoundAt(int i) {
          return r.getMidPointAt(i);
        }

        public void setSizeAt(int newSize, int i) {
          r.setSizeAt(newSize, i);
        }

      };
    } else {
      s = new Resizable2() {
        public int getElementCount() {
          return r.getElementCount();
        }

        public int getLowerBoundAt(int i) {
          return r.getMidPointAt(i);
        }

        public int getUpperBoundAt(int i) {
          return r.getUpperBoundAt(i);
        }

        public void setSizeAt(int newSize, int i) {
          r.setSizeAt(newSize, i);
        }

      };
    }
    adjustSizes(target, s, !inverse);
  }

  private void adjustSizes(long target, Resizable2 r, boolean limitToRange) {
    long totalLowerBound = 0;
    long totalUpperBound = 0;
    for (int i = 0; i < r.getElementCount(); i++) {
      totalLowerBound += r.getLowerBoundAt(i);
      totalUpperBound += r.getUpperBoundAt(i);
    }

    if (limitToRange) {
      target = Math.min(Math.max(totalLowerBound, target), totalUpperBound);
    }

    for (int i = 0; i < r.getElementCount(); i++) {
      int lowerBound = r.getLowerBoundAt(i);
      int upperBound = r.getUpperBoundAt(i);
      // Check for zero. This happens when the distribution of the delta
      // finishes early due to a series of "fixed" entries at the end.
      // In this case, lowerBound == upperBound, for all subsequent terms.
      int newSize;
      if (totalLowerBound == totalUpperBound) {
        newSize = lowerBound;
      } else {
        double f = (double) (target - totalLowerBound) / (totalUpperBound - totalLowerBound);
        newSize = (int) Math.round(lowerBound + f * (upperBound - lowerBound));
        // We'd need to round manually in an all integer version.
        // size[i] = (int)(((totalUpperBound - target) * lowerBound +
        //     (target - totalLowerBound) * upperBound)/(totalUpperBound-totalLowerBound));
      }
      r.setSizeAt(newSize, i);
      target -= newSize;
      totalLowerBound -= lowerBound;
      totalUpperBound -= upperBound;
    }
  }

  /**
   * Overrides <code>JComponent</code>'s <code>getToolTipText</code>
   * method in order to allow the renderer's tips to be used
   * if it has text set.
   * <p>
   * <b>Note:</b> For <code>JTable</code> to properly display
   * tooltips of its renderers
   * <code>JTable</code> must be a registered component with the
   * <code>ToolTipManager</code>.
   * This is done automatically in <code>initializeLocalVars</code>,
   * but if at a later point <code>JTable</code> is told
   * <code>setToolTipText(null)</code> it will unregister the table
   * component, and no tips from renderers will display anymore.
   *
   * @see JComponent#getToolTipText
   */
  public String getToolTipText(MouseEvent event) {
    String tip = null;
    Point p = event.getPoint();

    // Locate the renderer under the event location
    int hitColumnIndex = columnAtPoint(p);
    int hitRowIndex = rowAtPoint(p);

    if ((hitColumnIndex != -1) && (hitRowIndex != -1)) {
      TableCellRenderer renderer = getCellRenderer(hitRowIndex, hitColumnIndex);
      Component component = prepareRenderer(renderer, hitRowIndex, hitColumnIndex);

      // Now have to see if the component is a JComponent before
      // getting the tip
      if (component instanceof JComponent) {
        // Convert the event to the renderer's coordinate system
        Rectangle cellRect = getCellRect(hitRowIndex, hitColumnIndex, false);
        p.translate(-cellRect.x, -cellRect.y);
        MouseEvent newEvent = new MouseEvent(component, event.getID(),
            event.getWhen(), event.getModifiers(),
            p.x, p.y,
            event.getXOnScreen(),
            event.getYOnScreen(),
            event.getClickCount(),
            event.isPopupTrigger(),
            MouseEvent.NOBUTTON);

        tip = ((JComponent) component).getToolTipText(newEvent);
      }
    }

    // No tip from the renderer get our own tip
    if (tip == null) {
      tip = getToolTipText();
    }

    return tip;
  }

//
// Editing Support
//

  /**
   * Sets whether editors in this JTable get the keyboard focus
   * when an editor is activated as a result of the JTable
   * forwarding keyboard events for a cell.
   * By default, this property is false, and the JTable
   * retains the focus unless the cell is clicked.
   *
   * @param surrendersFocusOnKeystroke true if the editor should get the focus when keystrokes cause
   * the editor to be activated
   * @see #getSurrendersFocusOnKeystroke
   * @since 1.4
   */
  public void setSurrendersFocusOnKeystroke(boolean surrendersFocusOnKeystroke) {
    this.surrendersFocusOnKeystroke = surrendersFocusOnKeystroke;
  }

  /**
   * Returns true if the editor should get the focus
   * when keystrokes cause the editor to be activated
   *
   * @return true if the editor should get the focus when keystrokes cause the editor to be
   * activated
   * @see #setSurrendersFocusOnKeystroke
   * @since 1.4
   */
  public boolean getSurrendersFocusOnKeystroke() {
    return surrendersFocusOnKeystroke;
  }

  /**
   * Programmatically starts editing the cell at <code>row</code> and
   * <code>column</code>, if those indices are in the valid range, and
   * the cell at those indices is editable.
   * Note that this is a convenience method for
   * <code>editCellAt(int, int, null)</code>.
   *
   * @param row the row to be edited
   * @param column the column to be edited
   * @return false if for any reason the cell cannot be edited, or if the indices are invalid
   */
  public boolean editCellAt(int row, int column) {
    return editCellAt(row, column, null);
  }

  /**
   * Programmatically starts editing the cell at <code>row</code> and
   * <code>column</code>, if those indices are in the valid range, and
   * the cell at those indices is editable.
   * To prevent the <code>JTable</code> from
   * editing a particular table, column or cell value, return false from
   * the <code>isCellEditable</code> method in the <code>TableModel</code>
   * interface.
   *
   * @param row the row to be edited
   * @param column the column to be edited
   * @param e event to pass into <code>shouldSelectCell</code>; note that as of Java 2 platform
   * v1.2, the call to <code>shouldSelectCell</code> is no longer made
   * @return false if for any reason the cell cannot be edited, or if the indices are invalid
   */
  public boolean editCellAt(int row, int column, EventObject e) {
    if (cellEditor != null && !cellEditor.stopCellEditing()) {
      return false;
    }

    if (row < 0 || row >= getRowCount() ||
        column < 0 || column >= getColumnCount()) {
      return false;
    }

    if (!isCellEditable(row, column)) {
      return false;
    }

    if (editorRemover == null) {
      KeyboardFocusManager fm =
          KeyboardFocusManager.getCurrentKeyboardFocusManager();
      editorRemover = new CellEditorRemover(fm);
      fm.addPropertyChangeListener("permanentFocusOwner", editorRemover);
    }

    TableCellEditor editor = getCellEditor(row, column);
    if (editor != null && editor.isCellEditable(e)) {
      editorComp = prepareEditor(editor, row, column);
      if (editorComp == null) {
        removeEditor();
        return false;
      }
      editorComp.setBounds(getCellRect(row, column, false));
      add(editorComp);
      editorComp.validate();
      editorComp.repaint();

      setCellEditor(editor);
      setEditingRow(row);
      setEditingColumn(column);
      editor.addCellEditorListener(this);

      return true;
    }
    return false;
  }

  /**
   * Returns true if a cell is being edited.
   *
   * @return true if the table is editing a cell
   * @see #editingColumn
   * @see #editingRow
   */
  public boolean isEditing() {
    return cellEditor != null;
  }

  /**
   * Returns the component that is handling the editing session.
   * If nothing is being edited, returns null.
   *
   * @return Component handling editing session
   */
  public Component getEditorComponent() {
    return editorComp;
  }

  /**
   * Returns the index of the column that contains the cell currently
   * being edited.  If nothing is being edited, returns -1.
   *
   * @return the index of the column that contains the cell currently being edited; returns -1 if
   * nothing being edited
   * @see #editingRow
   */
  public int getEditingColumn() {
    return editingColumn;
  }

  /**
   * Returns the index of the row that contains the cell currently
   * being edited.  If nothing is being edited, returns -1.
   *
   * @return the index of the row that contains the cell currently being edited; returns -1 if
   * nothing being edited
   * @see #editingColumn
   */
  public int getEditingRow() {
    return editingRow;
  }

//
// Managing TableUI
//

  /**
   * Returns the L&amp;F object that renders this component.
   *
   * @return the <code>TableUI</code> object that renders this component
   */
  public TableUI getUI() {
    return (TableUI) ui;
  }

  /**
   * Sets the L&amp;F object that renders this component and repaints.
   *
   * @param ui the TableUI L&amp;F object
   * @beaninfo bound: true hidden: true attribute: visualUpdate true description: The UI object that
   * implements the Component's LookAndFeel.
   * @see UIDefaults#getUI
   */
  public void setUI(TableUI ui) {
    if (this.ui != ui) {
      super.setUI(ui);
      repaint();
    }
  }

  /**
   * Notification from the <code>UIManager</code> that the L&amp;F has changed.
   * Replaces the current UI object with the latest version from the
   * <code>UIManager</code>.
   *
   * @see JComponent#updateUI
   */
  public void updateUI() {
    // Update the UIs of the cell renderers, cell editors and header renderers.
    TableColumnModel cm = getColumnModel();
    for (int column = 0; column < cm.getColumnCount(); column++) {
      TableColumn aColumn = cm.getColumn(column);
      SwingUtilities.updateRendererOrEditorUI(aColumn.getCellRenderer());
      SwingUtilities.updateRendererOrEditorUI(aColumn.getCellEditor());
      SwingUtilities.updateRendererOrEditorUI(aColumn.getHeaderRenderer());
    }

    // Update the UIs of all the default renderers.
    Enumeration defaultRenderers = defaultRenderersByColumnClass.elements();
    while (defaultRenderers.hasMoreElements()) {
      SwingUtilities.updateRendererOrEditorUI(defaultRenderers.nextElement());
    }

    // Update the UIs of all the default editors.
    Enumeration defaultEditors = defaultEditorsByColumnClass.elements();
    while (defaultEditors.hasMoreElements()) {
      SwingUtilities.updateRendererOrEditorUI(defaultEditors.nextElement());
    }

    // Update the UI of the table header
    if (tableHeader != null && tableHeader.getParent() == null) {
      tableHeader.updateUI();
    }

    // Update UI applied to parent ScrollPane
    configureEnclosingScrollPaneUI();

    setUI((TableUI) UIManager.getUI(this));
  }

  /**
   * Returns the suffix used to construct the name of the L&amp;F class used to
   * render this component.
   *
   * @return the string "TableUI"
   * @see JComponent#getUIClassID
   * @see UIDefaults#getUI
   */
  public String getUIClassID() {
    return uiClassID;
  }

//
// Managing models
//

  /**
   * Sets the data model for this table to <code>newModel</code> and registers
   * with it for listener notifications from the new data model.
   *
   * @param dataModel the new data source for this table
   * @throws IllegalArgumentException if <code>newModel</code> is <code>null</code>
   * @beaninfo bound: true description: The model that is the source of the data for this view.
   * @see #getModel
   */
  public void setModel(TableModel dataModel) {
    if (dataModel == null) {
      throw new IllegalArgumentException("Cannot set a null TableModel");
    }
    if (this.dataModel != dataModel) {
      TableModel old = this.dataModel;
      if (old != null) {
        old.removeTableModelListener(this);
      }
      this.dataModel = dataModel;
      dataModel.addTableModelListener(this);

      tableChanged(new TableModelEvent(dataModel, TableModelEvent.HEADER_ROW));

      firePropertyChange("model", old, dataModel);

      if (getAutoCreateRowSorter()) {
        setRowSorter(new TableRowSorter<TableModel>(dataModel));
      }
    }
  }

  /**
   * Returns the <code>TableModel</code> that provides the data displayed by this
   * <code>JTable</code>.
   *
   * @return the <code>TableModel</code> that provides the data displayed by this
   * <code>JTable</code>
   * @see #setModel
   */
  public TableModel getModel() {
    return dataModel;
  }

  /**
   * Sets the column model for this table to <code>newModel</code> and registers
   * for listener notifications from the new column model. Also sets
   * the column model of the <code>JTableHeader</code> to <code>columnModel</code>.
   *
   * @param columnModel the new data source for this table
   * @throws IllegalArgumentException if <code>columnModel</code> is <code>null</code>
   * @beaninfo bound: true description: The object governing the way columns appear in the view.
   * @see #getColumnModel
   */
  public void setColumnModel(TableColumnModel columnModel) {
    if (columnModel == null) {
      throw new IllegalArgumentException("Cannot set a null ColumnModel");
    }
    TableColumnModel old = this.columnModel;
    if (columnModel != old) {
      if (old != null) {
        old.removeColumnModelListener(this);
      }
      this.columnModel = columnModel;
      columnModel.addColumnModelListener(this);

      // Set the column model of the header as well.
      if (tableHeader != null) {
        tableHeader.setColumnModel(columnModel);
      }

      firePropertyChange("columnModel", old, columnModel);
      resizeAndRepaint();
    }
  }

  /**
   * Returns the <code>TableColumnModel</code> that contains all column information
   * of this table.
   *
   * @return the object that provides the column state of the table
   * @see #setColumnModel
   */
  public TableColumnModel getColumnModel() {
    return columnModel;
  }

  /**
   * Sets the row selection model for this table to <code>newModel</code>
   * and registers for listener notifications from the new selection model.
   *
   * @param newModel the new selection model
   * @throws IllegalArgumentException if <code>newModel</code> is <code>null</code>
   * @beaninfo bound: true description: The selection model for rows.
   * @see #getSelectionModel
   */
  public void setSelectionModel(ListSelectionModel newModel) {
    if (newModel == null) {
      throw new IllegalArgumentException("Cannot set a null SelectionModel");
    }

    ListSelectionModel oldModel = selectionModel;

    if (newModel != oldModel) {
      if (oldModel != null) {
        oldModel.removeListSelectionListener(this);
      }

      selectionModel = newModel;
      newModel.addListSelectionListener(this);

      firePropertyChange("selectionModel", oldModel, newModel);
      repaint();
    }
  }

  /**
   * Returns the <code>ListSelectionModel</code> that is used to maintain row
   * selection state.
   *
   * @return the object that provides row selection state, <code>null</code> if row selection is not
   * allowed
   * @see #setSelectionModel
   */
  public ListSelectionModel getSelectionModel() {
    return selectionModel;
  }

//
// RowSorterListener
//

  /**
   * <code>RowSorterListener</code> notification that the
   * <code>RowSorter</code> has changed in some way.
   *
   * @param e the <code>RowSorterEvent</code> describing the change
   * @throws NullPointerException if <code>e</code> is <code>null</code>
   * @since 1.6
   */
  public void sorterChanged(RowSorterEvent e) {
    if (e.getType() == RowSorterEvent.Type.SORT_ORDER_CHANGED) {
      JTableHeader header = getTableHeader();
      if (header != null) {
        header.repaint();
      }
    } else if (e.getType() == RowSorterEvent.Type.SORTED) {
      sorterChanged = true;
      if (!ignoreSortChange) {
        sortedTableChanged(e, null);
      }
    }
  }


  /**
   * SortManager provides support for managing the selection and variable
   * row heights when sorting is enabled. This information is encapsulated
   * into a class to avoid bulking up JTable.
   */
  private final class SortManager {

    RowSorter<? extends TableModel> sorter;

    // Selection, in terms of the model. This is lazily created
    // as needed.
    private ListSelectionModel modelSelection;
    private int modelLeadIndex;
    // Set to true while in the process of changing the selection.
    // If this is true the selection change is ignored.
    private boolean syncingSelection;
    // Temporary cache of selection, in terms of model. This is only used
    // if we don't need the full weight of modelSelection.
    private int[] lastModelSelection;

    // Heights of the rows in terms of the model.
    private SizeSequence modelRowSizes;


    SortManager(RowSorter<? extends TableModel> sorter) {
      this.sorter = sorter;
      sorter.addRowSorterListener(JTable.this);
    }

    /**
     * Disposes any resources used by this SortManager.
     */
    public void dispose() {
      if (sorter != null) {
        sorter.removeRowSorterListener(JTable.this);
      }
    }

    /**
     * Sets the height for a row at a specified index.
     */
    public void setViewRowHeight(int viewIndex, int rowHeight) {
      if (modelRowSizes == null) {
        modelRowSizes = new SizeSequence(getModel().getRowCount(),
            getRowHeight());
      }
      modelRowSizes.setSize(convertRowIndexToModel(viewIndex), rowHeight);
    }

    /**
     * Invoked when the underlying model has completely changed.
     */
    public void allChanged() {
      modelLeadIndex = -1;
      modelSelection = null;
      modelRowSizes = null;
    }

    /**
     * Invoked when the selection, on the view, has changed.
     */
    public void viewSelectionChanged(ListSelectionEvent e) {
      if (!syncingSelection && modelSelection != null) {
        modelSelection = null;
      }
    }

    /**
     * Invoked when either the table model has changed, or the RowSorter
     * has changed. This is invoked prior to notifying the sorter of the
     * change.
     */
    public void prepareForChange(RowSorterEvent sortEvent,
        ModelChange change) {
      if (getUpdateSelectionOnSort()) {
        cacheSelection(sortEvent, change);
      }
    }

    /**
     * Updates the internal cache of the selection based on the change.
     */
    private void cacheSelection(RowSorterEvent sortEvent,
        ModelChange change) {
      if (sortEvent != null) {
        // sort order changed. If modelSelection is null and filtering
        // is enabled we need to cache the selection in terms of the
        // underlying model, this will allow us to correctly restore
        // the selection even if rows are filtered out.
        if (modelSelection == null &&
            sorter.getViewRowCount() != getModel().getRowCount()) {
          modelSelection = new DefaultListSelectionModel();
          ListSelectionModel viewSelection = getSelectionModel();
          int min = viewSelection.getMinSelectionIndex();
          int max = viewSelection.getMaxSelectionIndex();
          int modelIndex;
          for (int viewIndex = min; viewIndex <= max; viewIndex++) {
            if (viewSelection.isSelectedIndex(viewIndex)) {
              modelIndex = convertRowIndexToModel(
                  sortEvent, viewIndex);
              if (modelIndex != -1) {
                modelSelection.addSelectionInterval(
                    modelIndex, modelIndex);
              }
            }
          }
          modelIndex = convertRowIndexToModel(sortEvent,
              viewSelection.getLeadSelectionIndex());
          SwingUtilities2.setLeadAnchorWithoutSelection(
              modelSelection, modelIndex, modelIndex);
        } else if (modelSelection == null) {
          // Sorting changed, haven't cached selection in terms
          // of model and no filtering. Temporarily cache selection.
          cacheModelSelection(sortEvent);
        }
      } else if (change.allRowsChanged) {
        // All the rows have changed, chuck any cached selection.
        modelSelection = null;
      } else if (modelSelection != null) {
        // Table changed, reflect changes in cached selection model.
        switch (change.type) {
          case TableModelEvent.DELETE:
            modelSelection.removeIndexInterval(change.startModelIndex,
                change.endModelIndex);
            break;
          case TableModelEvent.INSERT:
            modelSelection.insertIndexInterval(change.startModelIndex,
                change.length,
                true);
            break;
          default:
            break;
        }
      } else {
        // table changed, but haven't cached rows, temporarily
        // cache them.
        cacheModelSelection(null);
      }
    }

    private void cacheModelSelection(RowSorterEvent sortEvent) {
      lastModelSelection = convertSelectionToModel(sortEvent);
      modelLeadIndex = convertRowIndexToModel(sortEvent,
          selectionModel.getLeadSelectionIndex());
    }

    /**
     * Inovked when either the table has changed or the sorter has changed
     * and after the sorter has been notified. If necessary this will
     * reapply the selection and variable row heights.
     */
    public void processChange(RowSorterEvent sortEvent,
        ModelChange change,
        boolean sorterChanged) {
      if (change != null) {
        if (change.allRowsChanged) {
          modelRowSizes = null;
          rowModel = null;
        } else if (modelRowSizes != null) {
          if (change.type == TableModelEvent.INSERT) {
            modelRowSizes.insertEntries(change.startModelIndex,
                change.endModelIndex -
                    change.startModelIndex + 1,
                getRowHeight());
          } else if (change.type == TableModelEvent.DELETE) {
            modelRowSizes.removeEntries(change.startModelIndex,
                change.endModelIndex -
                    change.startModelIndex + 1);
          }
        }
      }
      if (sorterChanged) {
        setViewRowHeightsFromModel();
        restoreSelection(change);
      }
    }

    /**
     * Resets the variable row heights in terms of the view from
     * that of the variable row heights in terms of the model.
     */
    private void setViewRowHeightsFromModel() {
      if (modelRowSizes != null) {
        rowModel.setSizes(getRowCount(), getRowHeight());
        for (int viewIndex = getRowCount() - 1; viewIndex >= 0;
            viewIndex--) {
          int modelIndex = convertRowIndexToModel(viewIndex);
          rowModel.setSize(viewIndex,
              modelRowSizes.getSize(modelIndex));
        }
      }
    }

    /**
     * Restores the selection from that in terms of the model.
     */
    private void restoreSelection(ModelChange change) {
      syncingSelection = true;
      if (lastModelSelection != null) {
        restoreSortingSelection(lastModelSelection,
            modelLeadIndex, change);
        lastModelSelection = null;
      } else if (modelSelection != null) {
        ListSelectionModel viewSelection = getSelectionModel();
        viewSelection.setValueIsAdjusting(true);
        viewSelection.clearSelection();
        int min = modelSelection.getMinSelectionIndex();
        int max = modelSelection.getMaxSelectionIndex();
        int viewIndex;
        for (int modelIndex = min; modelIndex <= max; modelIndex++) {
          if (modelSelection.isSelectedIndex(modelIndex)) {
            viewIndex = convertRowIndexToView(modelIndex);
            if (viewIndex != -1) {
              viewSelection.addSelectionInterval(viewIndex,
                  viewIndex);
            }
          }
        }
        // Restore the lead
        int viewLeadIndex = modelSelection.getLeadSelectionIndex();
        if (viewLeadIndex != -1 && !modelSelection.isSelectionEmpty()) {
          viewLeadIndex = convertRowIndexToView(viewLeadIndex);
        }
        SwingUtilities2.setLeadAnchorWithoutSelection(
            viewSelection, viewLeadIndex, viewLeadIndex);
        viewSelection.setValueIsAdjusting(false);
      }
      syncingSelection = false;
    }
  }


  /**
   * ModelChange is used when sorting to restore state, it corresponds
   * to data from a TableModelEvent.  The values are precalculated as
   * they are used extensively.
   */
  private final class ModelChange {

    // Starting index of the change, in terms of the model
    int startModelIndex;

    // Ending index of the change, in terms of the model
    int endModelIndex;

    // Type of change
    int type;

    // Number of rows in the model
    int modelRowCount;

    // The event that triggered this.
    TableModelEvent event;

    // Length of the change (end - start + 1)
    int length;

    // True if the event indicates all the contents have changed
    boolean allRowsChanged;

    ModelChange(TableModelEvent e) {
      startModelIndex = Math.max(0, e.getFirstRow());
      endModelIndex = e.getLastRow();
      modelRowCount = getModel().getRowCount();
      if (endModelIndex < 0) {
        endModelIndex = Math.max(0, modelRowCount - 1);
      }
      length = endModelIndex - startModelIndex + 1;
      type = e.getType();
      event = e;
      allRowsChanged = (e.getLastRow() == Integer.MAX_VALUE);
    }
  }

  /**
   * Invoked when <code>sorterChanged</code> is invoked, or
   * when <code>tableChanged</code> is invoked and sorting is enabled.
   */
  private void sortedTableChanged(RowSorterEvent sortedEvent,
      TableModelEvent e) {
    int editingModelIndex = -1;
    ModelChange change = (e != null) ? new ModelChange(e) : null;

    if ((change == null || !change.allRowsChanged) &&
        this.editingRow != -1) {
      editingModelIndex = convertRowIndexToModel(sortedEvent,
          this.editingRow);
    }

    sortManager.prepareForChange(sortedEvent, change);

    if (e != null) {
      if (change.type == TableModelEvent.UPDATE) {
        repaintSortedRows(change);
      }
      notifySorter(change);
      if (change.type != TableModelEvent.UPDATE) {
        // If the Sorter is unsorted we will not have received
        // notification, force treating insert/delete as a change.
        sorterChanged = true;
      }
    } else {
      sorterChanged = true;
    }

    sortManager.processChange(sortedEvent, change, sorterChanged);

    if (sorterChanged) {
      // Update the editing row
      if (this.editingRow != -1) {
        int newIndex = (editingModelIndex == -1) ? -1 :
            convertRowIndexToView(editingModelIndex, change);
        restoreSortingEditingRow(newIndex);
      }

      // And handle the appropriate repainting.
      if (e == null || change.type != TableModelEvent.UPDATE) {
        resizeAndRepaint();
      }
    }

    // Check if lead/anchor need to be reset.
    if (change != null && change.allRowsChanged) {
      clearSelectionAndLeadAnchor();
      resizeAndRepaint();
    }
  }

  /**
   * Repaints the sort of sorted rows in response to a TableModelEvent.
   */
  private void repaintSortedRows(ModelChange change) {
    if (change.startModelIndex > change.endModelIndex ||
        change.startModelIndex + 10 < change.endModelIndex) {
      // Too much has changed, punt
      repaint();
      return;
    }
    int eventColumn = change.event.getColumn();
    int columnViewIndex = eventColumn;
    if (columnViewIndex == TableModelEvent.ALL_COLUMNS) {
      columnViewIndex = 0;
    } else {
      columnViewIndex = convertColumnIndexToView(columnViewIndex);
      if (columnViewIndex == -1) {
        return;
      }
    }
    int modelIndex = change.startModelIndex;
    while (modelIndex <= change.endModelIndex) {
      int viewIndex = convertRowIndexToView(modelIndex++);
      if (viewIndex != -1) {
        Rectangle dirty = getCellRect(viewIndex, columnViewIndex,
            false);
        int x = dirty.x;
        int w = dirty.width;
        if (eventColumn == TableModelEvent.ALL_COLUMNS) {
          x = 0;
          w = getWidth();
        }
        repaint(x, dirty.y, w, dirty.height);
      }
    }
  }

  /**
   * Restores the selection after a model event/sort order changes.
   * All coordinates are in terms of the model.
   */
  private void restoreSortingSelection(int[] selection, int lead,
      ModelChange change) {
    // Convert the selection from model to view
    for (int i = selection.length - 1; i >= 0; i--) {
      selection[i] = convertRowIndexToView(selection[i], change);
    }
    lead = convertRowIndexToView(lead, change);

    // Check for the common case of no change in selection for 1 row
    if (selection.length == 0 ||
        (selection.length == 1 && selection[0] == getSelectedRow())) {
      return;
    }

    // And apply the new selection
    selectionModel.setValueIsAdjusting(true);
    selectionModel.clearSelection();
    for (int i = selection.length - 1; i >= 0; i--) {
      if (selection[i] != -1) {
        selectionModel.addSelectionInterval(selection[i],
            selection[i]);
      }
    }
    SwingUtilities2.setLeadAnchorWithoutSelection(
        selectionModel, lead, lead);
    selectionModel.setValueIsAdjusting(false);
  }

  /**
   * Restores the editing row after a model event/sort order change.
   *
   * @param editingRow new index of the editingRow, in terms of the view
   */
  private void restoreSortingEditingRow(int editingRow) {
    if (editingRow == -1) {
      // Editing row no longer being shown, cancel editing
      TableCellEditor editor = getCellEditor();
      if (editor != null) {
        // First try and cancel
        editor.cancelCellEditing();
        if (getCellEditor() != null) {
          // CellEditor didn't cede control, forcefully
          // remove it
          removeEditor();
        }
      }
    } else {
      // Repositioning handled in BasicTableUI
      this.editingRow = editingRow;
      repaint();
    }
  }

  /**
   * Notifies the sorter of a change in the underlying model.
   */
  private void notifySorter(ModelChange change) {
    try {
      ignoreSortChange = true;
      sorterChanged = false;
      switch (change.type) {
        case TableModelEvent.UPDATE:
          if (change.event.getLastRow() == Integer.MAX_VALUE) {
            sortManager.sorter.allRowsChanged();
          } else if (change.event.getColumn() ==
              TableModelEvent.ALL_COLUMNS) {
            sortManager.sorter.rowsUpdated(change.startModelIndex,
                change.endModelIndex);
          } else {
            sortManager.sorter.rowsUpdated(change.startModelIndex,
                change.endModelIndex,
                change.event.getColumn());
          }
          break;
        case TableModelEvent.INSERT:
          sortManager.sorter.rowsInserted(change.startModelIndex,
              change.endModelIndex);
          break;
        case TableModelEvent.DELETE:
          sortManager.sorter.rowsDeleted(change.startModelIndex,
              change.endModelIndex);
          break;
      }
    } finally {
      ignoreSortChange = false;
    }
  }

  /**
   * Converts a model index to view index.  This is called when the
   * sorter or model changes and sorting is enabled.
   *
   * @param change describes the TableModelEvent that initiated the change; will be null if called
   * as the result of a sort
   */
  private int convertRowIndexToView(int modelIndex, ModelChange change) {
    if (modelIndex < 0) {
      return -1;
    }
    if (change != null && modelIndex >= change.startModelIndex) {
      if (change.type == TableModelEvent.INSERT) {
        if (modelIndex + change.length >= change.modelRowCount) {
          return -1;
        }
        return sortManager.sorter.convertRowIndexToView(
            modelIndex + change.length);
      } else if (change.type == TableModelEvent.DELETE) {
        if (modelIndex <= change.endModelIndex) {
          // deleted
          return -1;
        } else {
          if (modelIndex - change.length >= change.modelRowCount) {
            return -1;
          }
          return sortManager.sorter.convertRowIndexToView(
              modelIndex - change.length);
        }
      }
      // else, updated
    }
    if (modelIndex >= getModel().getRowCount()) {
      return -1;
    }
    return sortManager.sorter.convertRowIndexToView(modelIndex);
  }

  /**
   * Converts the selection to model coordinates.  This is used when
   * the model changes or the sorter changes.
   */
  private int[] convertSelectionToModel(RowSorterEvent e) {
    int[] selection = getSelectedRows();
    for (int i = selection.length - 1; i >= 0; i--) {
      selection[i] = convertRowIndexToModel(e, selection[i]);
    }
    return selection;
  }

  private int convertRowIndexToModel(RowSorterEvent e, int viewIndex) {
    if (e != null) {
      if (e.getPreviousRowCount() == 0) {
        return viewIndex;
      }
      // range checking handled by RowSorterEvent
      return e.convertPreviousRowIndexToModel(viewIndex);
    }
    // Make sure the viewIndex is valid
    if (viewIndex < 0 || viewIndex >= getRowCount()) {
      return -1;
    }
    return convertRowIndexToModel(viewIndex);
  }

//
// Implementing TableModelListener interface
//

  /**
   * Invoked when this table's <code>TableModel</code> generates
   * a <code>TableModelEvent</code>.
   * The <code>TableModelEvent</code> should be constructed in the
   * coordinate system of the model; the appropriate mapping to the
   * view coordinate system is performed by this <code>JTable</code>
   * when it receives the event.
   * <p>
   * Application code will not use these methods explicitly, they
   * are used internally by <code>JTable</code>.
   * <p>
   * Note that as of 1.3, this method clears the selection, if any.
   */
  public void tableChanged(TableModelEvent e) {
    if (e == null || e.getFirstRow() == TableModelEvent.HEADER_ROW) {
      // The whole thing changed
      clearSelectionAndLeadAnchor();

      rowModel = null;

      if (sortManager != null) {
        try {
          ignoreSortChange = true;
          sortManager.sorter.modelStructureChanged();
        } finally {
          ignoreSortChange = false;
        }
        sortManager.allChanged();
      }

      if (getAutoCreateColumnsFromModel()) {
        // This will effect invalidation of the JTable and JTableHeader.
        createDefaultColumnsFromModel();
        return;
      }

      resizeAndRepaint();
      return;
    }

    if (sortManager != null) {
      sortedTableChanged(null, e);
      return;
    }

    // The totalRowHeight calculated below will be incorrect if
    // there are variable height rows. Repaint the visible region,
    // but don't return as a revalidate may be necessary as well.
    if (rowModel != null) {
      repaint();
    }

    if (e.getType() == TableModelEvent.INSERT) {
      tableRowsInserted(e);
      return;
    }

    if (e.getType() == TableModelEvent.DELETE) {
      tableRowsDeleted(e);
      return;
    }

    int modelColumn = e.getColumn();
    int start = e.getFirstRow();
    int end = e.getLastRow();

    Rectangle dirtyRegion;
    if (modelColumn == TableModelEvent.ALL_COLUMNS) {
      // 1 or more rows changed
      dirtyRegion = new Rectangle(0, start * getRowHeight(),
          getColumnModel().getTotalColumnWidth(), 0);
    } else {
      // A cell or column of cells has changed.
      // Unlike the rest of the methods in the JTable, the TableModelEvent
      // uses the coordinate system of the model instead of the view.
      // This is the only place in the JTable where this "reverse mapping"
      // is used.
      int column = convertColumnIndexToView(modelColumn);
      dirtyRegion = getCellRect(start, column, false);
    }

    // Now adjust the height of the dirty region according to the value of "end".
    // Check for Integer.MAX_VALUE as this will cause an overflow.
    if (end != Integer.MAX_VALUE) {
      dirtyRegion.height = (end - start + 1) * getRowHeight();
      repaint(dirtyRegion.x, dirtyRegion.y, dirtyRegion.width, dirtyRegion.height);
    }
    // In fact, if the end is Integer.MAX_VALUE we need to revalidate anyway
    // because the scrollbar may need repainting.
    else {
      clearSelectionAndLeadAnchor();
      resizeAndRepaint();
      rowModel = null;
    }
  }

  /*
     * Invoked when rows have been inserted into the table.
     * <p>
     * Application code will not use these methods explicitly, they
     * are used internally by JTable.
     *
     * @param e the TableModelEvent encapsulating the insertion
     */
  private void tableRowsInserted(TableModelEvent e) {
    int start = e.getFirstRow();
    int end = e.getLastRow();
    if (start < 0) {
      start = 0;
    }
    if (end < 0) {
      end = getRowCount() - 1;
    }

    // Adjust the selection to account for the new rows.
    int length = end - start + 1;
    selectionModel.insertIndexInterval(start, length, true);

    // If we have variable height rows, adjust the row model.
    if (rowModel != null) {
      rowModel.insertEntries(start, length, getRowHeight());
    }
    int rh = getRowHeight();
    Rectangle drawRect = new Rectangle(0, start * rh,
        getColumnModel().getTotalColumnWidth(),
        (getRowCount() - start) * rh);

    revalidate();
    // PENDING(milne) revalidate calls repaint() if parent is a ScrollPane
    // repaint still required in the unusual case where there is no ScrollPane
    repaint(drawRect);
  }

  /*
     * Invoked when rows have been removed from the table.
     * <p>
     * Application code will not use these methods explicitly, they
     * are used internally by JTable.
     *
     * @param e the TableModelEvent encapsulating the deletion
     */
  private void tableRowsDeleted(TableModelEvent e) {
    int start = e.getFirstRow();
    int end = e.getLastRow();
    if (start < 0) {
      start = 0;
    }
    if (end < 0) {
      end = getRowCount() - 1;
    }

    int deletedCount = end - start + 1;
    int previousRowCount = getRowCount() + deletedCount;
    // Adjust the selection to account for the new rows
    selectionModel.removeIndexInterval(start, end);

    // If we have variable height rows, adjust the row model.
    if (rowModel != null) {
      rowModel.removeEntries(start, deletedCount);
    }

    int rh = getRowHeight();
    Rectangle drawRect = new Rectangle(0, start * rh,
        getColumnModel().getTotalColumnWidth(),
        (previousRowCount - start) * rh);

    revalidate();
    // PENDING(milne) revalidate calls repaint() if parent is a ScrollPane
    // repaint still required in the unusual case where there is no ScrollPane
    repaint(drawRect);
  }

//
// Implementing TableColumnModelListener interface
//

  /**
   * Invoked when a column is added to the table column model.
   * <p>
   * Application code will not use these methods explicitly, they
   * are used internally by JTable.
   *
   * @see TableColumnModelListener
   */
  public void columnAdded(TableColumnModelEvent e) {
    // If I'm currently editing, then I should stop editing
    if (isEditing()) {
      removeEditor();
    }
    resizeAndRepaint();
  }

  /**
   * Invoked when a column is removed from the table column model.
   * <p>
   * Application code will not use these methods explicitly, they
   * are used internally by JTable.
   *
   * @see TableColumnModelListener
   */
  public void columnRemoved(TableColumnModelEvent e) {
    // If I'm currently editing, then I should stop editing
    if (isEditing()) {
      removeEditor();
    }
    resizeAndRepaint();
  }

  /**
   * Invoked when a column is repositioned. If a cell is being
   * edited, then editing is stopped and the cell is redrawn.
   * <p>
   * Application code will not use these methods explicitly, they
   * are used internally by JTable.
   *
   * @param e the event received
   * @see TableColumnModelListener
   */
  public void columnMoved(TableColumnModelEvent e) {
    if (isEditing() && !getCellEditor().stopCellEditing()) {
      getCellEditor().cancelCellEditing();
    }
    repaint();
  }

  /**
   * Invoked when a column is moved due to a margin change.
   * If a cell is being edited, then editing is stopped and the cell
   * is redrawn.
   * <p>
   * Application code will not use these methods explicitly, they
   * are used internally by JTable.
   *
   * @param e the event received
   * @see TableColumnModelListener
   */
  public void columnMarginChanged(ChangeEvent e) {
    if (isEditing() && !getCellEditor().stopCellEditing()) {
      getCellEditor().cancelCellEditing();
    }
    TableColumn resizingColumn = getResizingColumn();
    // Need to do this here, before the parent's
    // layout manager calls getPreferredSize().
    if (resizingColumn != null && autoResizeMode == AUTO_RESIZE_OFF) {
      resizingColumn.setPreferredWidth(resizingColumn.getWidth());
    }
    resizeAndRepaint();
  }

  private int limit(int i, int a, int b) {
    return Math.min(b, Math.max(i, a));
  }

  /**
   * Invoked when the selection model of the <code>TableColumnModel</code>
   * is changed.
   * <p>
   * Application code will not use these methods explicitly, they
   * are used internally by JTable.
   *
   * @param e the event received
   * @see TableColumnModelListener
   */
  public void columnSelectionChanged(ListSelectionEvent e) {
    boolean isAdjusting = e.getValueIsAdjusting();
    if (columnSelectionAdjusting && !isAdjusting) {
      // The assumption is that when the model is no longer adjusting
      // we will have already gotten all the changes, and therefore
      // don't need to do an additional paint.
      columnSelectionAdjusting = false;
      return;
    }
    columnSelectionAdjusting = isAdjusting;
    // The getCellRect() call will fail unless there is at least one row.
    if (getRowCount() <= 0 || getColumnCount() <= 0) {
      return;
    }
    int firstIndex = limit(e.getFirstIndex(), 0, getColumnCount() - 1);
    int lastIndex = limit(e.getLastIndex(), 0, getColumnCount() - 1);
    int minRow = 0;
    int maxRow = getRowCount() - 1;
    if (getRowSelectionAllowed()) {
      minRow = selectionModel.getMinSelectionIndex();
      maxRow = selectionModel.getMaxSelectionIndex();
      int leadRow = getAdjustedIndex(selectionModel.getLeadSelectionIndex(), true);

      if (minRow == -1 || maxRow == -1) {
        if (leadRow == -1) {
          // nothing to repaint, return
          return;
        }

        // only thing to repaint is the lead
        minRow = maxRow = leadRow;
      } else {
        // We need to consider more than just the range between
        // the min and max selected index. The lead row, which could
        // be outside this range, should be considered also.
        if (leadRow != -1) {
          minRow = Math.min(minRow, leadRow);
          maxRow = Math.max(maxRow, leadRow);
        }
      }
    }
    Rectangle firstColumnRect = getCellRect(minRow, firstIndex, false);
    Rectangle lastColumnRect = getCellRect(maxRow, lastIndex, false);
    Rectangle dirtyRegion = firstColumnRect.union(lastColumnRect);
    repaint(dirtyRegion);
  }

//
// Implementing ListSelectionListener interface
//

  /**
   * Invoked when the row selection changes -- repaints to show the new
   * selection.
   * <p>
   * Application code will not use these methods explicitly, they
   * are used internally by JTable.
   *
   * @param e the event received
   * @see ListSelectionListener
   */
  public void valueChanged(ListSelectionEvent e) {
    if (sortManager != null) {
      sortManager.viewSelectionChanged(e);
    }
    boolean isAdjusting = e.getValueIsAdjusting();
    if (rowSelectionAdjusting && !isAdjusting) {
      // The assumption is that when the model is no longer adjusting
      // we will have already gotten all the changes, and therefore
      // don't need to do an additional paint.
      rowSelectionAdjusting = false;
      return;
    }
    rowSelectionAdjusting = isAdjusting;
    // The getCellRect() calls will fail unless there is at least one column.
    if (getRowCount() <= 0 || getColumnCount() <= 0) {
      return;
    }
    int firstIndex = limit(e.getFirstIndex(), 0, getRowCount() - 1);
    int lastIndex = limit(e.getLastIndex(), 0, getRowCount() - 1);
    Rectangle firstRowRect = getCellRect(firstIndex, 0, false);
    Rectangle lastRowRect = getCellRect(lastIndex, getColumnCount() - 1, false);
    Rectangle dirtyRegion = firstRowRect.union(lastRowRect);
    repaint(dirtyRegion);
  }

//
// Implementing the CellEditorListener interface
//

  /**
   * Invoked when editing is finished. The changes are saved and the
   * editor is discarded.
   * <p>
   * Application code will not use these methods explicitly, they
   * are used internally by JTable.
   *
   * @param e the event received
   * @see CellEditorListener
   */
  public void editingStopped(ChangeEvent e) {
    // Take in the new value
    TableCellEditor editor = getCellEditor();
    if (editor != null) {
      Object value = editor.getCellEditorValue();
      setValueAt(value, editingRow, editingColumn);
      removeEditor();
    }
  }

  /**
   * Invoked when editing is canceled. The editor object is discarded
   * and the cell is rendered once again.
   * <p>
   * Application code will not use these methods explicitly, they
   * are used internally by JTable.
   *
   * @param e the event received
   * @see CellEditorListener
   */
  public void editingCanceled(ChangeEvent e) {
    removeEditor();
  }

//
// Implementing the Scrollable interface
//

  /**
   * Sets the preferred size of the viewport for this table.
   *
   * @param size a <code>Dimension</code> object specifying the <code>preferredSize</code> of a
   * <code>JViewport</code> whose view is this table
   * @beaninfo description: The preferred size of the viewport.
   * @see Scrollable#getPreferredScrollableViewportSize
   */
  public void setPreferredScrollableViewportSize(Dimension size) {
    preferredViewportSize = size;
  }

  /**
   * Returns the preferred size of the viewport for this table.
   *
   * @return a <code>Dimension</code> object containing the <code>preferredSize</code> of the
   * <code>JViewport</code> which displays this table
   * @see Scrollable#getPreferredScrollableViewportSize
   */
  public Dimension getPreferredScrollableViewportSize() {
    return preferredViewportSize;
  }

  /**
   * Returns the scroll increment (in pixels) that completely exposes one new
   * row or column (depending on the orientation).
   * <p>
   * This method is called each time the user requests a unit scroll.
   *
   * @param visibleRect the view area visible within the viewport
   * @param orientation either <code>SwingConstants.VERTICAL</code> or
   * <code>SwingConstants.HORIZONTAL</code>
   * @param direction less than zero to scroll up/left, greater than zero for down/right
   * @return the "unit" increment for scrolling in the specified direction
   * @see Scrollable#getScrollableUnitIncrement
   */
  public int getScrollableUnitIncrement(Rectangle visibleRect,
      int orientation,
      int direction) {
    int leadingRow;
    int leadingCol;
    Rectangle leadingCellRect;

    int leadingVisibleEdge;
    int leadingCellEdge;
    int leadingCellSize;

    leadingRow = getLeadingRow(visibleRect);
    leadingCol = getLeadingCol(visibleRect);
    if (orientation == SwingConstants.VERTICAL && leadingRow < 0) {
      // Couldn't find leading row - return some default value
      return getRowHeight();
    } else if (orientation == SwingConstants.HORIZONTAL && leadingCol < 0) {
      // Couldn't find leading col - return some default value
      return 100;
    }

    // Note that it's possible for one of leadingCol or leadingRow to be
    // -1, depending on the orientation.  This is okay, as getCellRect()
    // still provides enough information to calculate the unit increment.
    leadingCellRect = getCellRect(leadingRow, leadingCol, true);
    leadingVisibleEdge = leadingEdge(visibleRect, orientation);
    leadingCellEdge = leadingEdge(leadingCellRect, orientation);

    if (orientation == SwingConstants.VERTICAL) {
      leadingCellSize = leadingCellRect.height;

    } else {
      leadingCellSize = leadingCellRect.width;
    }

    // 4 cases:
    // #1: Leading cell fully visible, reveal next cell
    // #2: Leading cell fully visible, hide leading cell
    // #3: Leading cell partially visible, hide rest of leading cell
    // #4: Leading cell partially visible, reveal rest of leading cell

    if (leadingVisibleEdge == leadingCellEdge) { // Leading cell is fully
      // visible
      // Case #1: Reveal previous cell
      if (direction < 0) {
        int retVal = 0;

        if (orientation == SwingConstants.VERTICAL) {
          // Loop past any zero-height rows
          while (--leadingRow >= 0) {
            retVal = getRowHeight(leadingRow);
            if (retVal != 0) {
              break;
            }
          }
        } else { // HORIZONTAL
          // Loop past any zero-width cols
          while (--leadingCol >= 0) {
            retVal = getCellRect(leadingRow, leadingCol, true).width;
            if (retVal != 0) {
              break;
            }
          }
        }
        return retVal;
      } else { // Case #2: hide leading cell
        return leadingCellSize;
      }
    } else { // Leading cell is partially hidden
      // Compute visible, hidden portions
      int hiddenAmt = Math.abs(leadingVisibleEdge - leadingCellEdge);
      int visibleAmt = leadingCellSize - hiddenAmt;

      if (direction > 0) {
        // Case #3: hide showing portion of leading cell
        return visibleAmt;
      } else { // Case #4: reveal hidden portion of leading cell
        return hiddenAmt;
      }
    }
  }

  /**
   * Returns <code>visibleRect.height</code> or
   * <code>visibleRect.width</code>,
   * depending on this table's orientation.  Note that as of Swing 1.1.1
   * (Java 2 v 1.2.2) the value
   * returned will ensure that the viewport is cleanly aligned on
   * a row boundary.
   *
   * @return <code>visibleRect.height</code> or <code>visibleRect.width</code> per the orientation
   * @see Scrollable#getScrollableBlockIncrement
   */
  public int getScrollableBlockIncrement(Rectangle visibleRect,
      int orientation, int direction) {

    if (getRowCount() == 0) {
      // Short-circuit empty table model
      if (SwingConstants.VERTICAL == orientation) {
        int rh = getRowHeight();
        return (rh > 0) ? Math.max(rh, (visibleRect.height / rh) * rh) :
            visibleRect.height;
      } else {
        return visibleRect.width;
      }
    }
    // Shortcut for vertical scrolling of a table w/ uniform row height
    if (null == rowModel && SwingConstants.VERTICAL == orientation) {
      int row = rowAtPoint(visibleRect.getLocation());
      assert row != -1;
      int col = columnAtPoint(visibleRect.getLocation());
      Rectangle cellRect = getCellRect(row, col, true);

      if (cellRect.y == visibleRect.y) {
        int rh = getRowHeight();
        assert rh > 0;
        return Math.max(rh, (visibleRect.height / rh) * rh);
      }
    }
    if (direction < 0) {
      return getPreviousBlockIncrement(visibleRect, orientation);
    } else {
      return getNextBlockIncrement(visibleRect, orientation);
    }
  }

  /**
   * Called to get the block increment for upward scrolling in cases of
   * horizontal scrolling, or for vertical scrolling of a table with
   * variable row heights.
   */
  private int getPreviousBlockIncrement(Rectangle visibleRect,
      int orientation) {
    // Measure back from visible leading edge
    // If we hit the cell on its leading edge, it becomes the leading cell.
    // Else, use following cell

    int row;
    int col;

    int newEdge;
    Point newCellLoc;

    int visibleLeadingEdge = leadingEdge(visibleRect, orientation);
    boolean leftToRight = getComponentOrientation().isLeftToRight();
    int newLeadingEdge;

    // Roughly determine the new leading edge by measuring back from the
    // leading visible edge by the size of the visible rect, and find the
    // cell there.
    if (orientation == SwingConstants.VERTICAL) {
      newEdge = visibleLeadingEdge - visibleRect.height;
      int x = visibleRect.x + (leftToRight ? 0 : visibleRect.width);
      newCellLoc = new Point(x, newEdge);
    } else if (leftToRight) {
      newEdge = visibleLeadingEdge - visibleRect.width;
      newCellLoc = new Point(newEdge, visibleRect.y);
    } else { // Horizontal, right-to-left
      newEdge = visibleLeadingEdge + visibleRect.width;
      newCellLoc = new Point(newEdge - 1, visibleRect.y);
    }
    row = rowAtPoint(newCellLoc);
    col = columnAtPoint(newCellLoc);

    // If we're measuring past the beginning of the table, we get an invalid
    // cell.  Just go to the beginning of the table in this case.
    if (orientation == SwingConstants.VERTICAL & row < 0) {
      newLeadingEdge = 0;
    } else if (orientation == SwingConstants.HORIZONTAL & col < 0) {
      if (leftToRight) {
        newLeadingEdge = 0;
      } else {
        newLeadingEdge = getWidth();
      }
    } else {
      // Refine our measurement
      Rectangle newCellRect = getCellRect(row, col, true);
      int newCellLeadingEdge = leadingEdge(newCellRect, orientation);
      int newCellTrailingEdge = trailingEdge(newCellRect, orientation);

      // Usually, we hit in the middle of newCell, and want to scroll to
      // the beginning of the cell after newCell.  But there are a
      // couple corner cases where we want to scroll to the beginning of
      // newCell itself.  These cases are:
      // 1) newCell is so large that it ends at or extends into the
      //    visibleRect (newCell is the leading cell, or is adjacent to
      //    the leading cell)
      // 2) newEdge happens to fall right on the beginning of a cell

      // Case 1
      if ((orientation == SwingConstants.VERTICAL || leftToRight) &&
          (newCellTrailingEdge >= visibleLeadingEdge)) {
        newLeadingEdge = newCellLeadingEdge;
      } else if (orientation == SwingConstants.HORIZONTAL &&
          !leftToRight &&
          newCellTrailingEdge <= visibleLeadingEdge) {
        newLeadingEdge = newCellLeadingEdge;
      }
      // Case 2:
      else if (newEdge == newCellLeadingEdge) {
        newLeadingEdge = newCellLeadingEdge;
      }
      // Common case: scroll to cell after newCell
      else {
        newLeadingEdge = newCellTrailingEdge;
      }
    }
    return Math.abs(visibleLeadingEdge - newLeadingEdge);
  }

  /**
   * Called to get the block increment for downward scrolling in cases of
   * horizontal scrolling, or for vertical scrolling of a table with
   * variable row heights.
   */
  private int getNextBlockIncrement(Rectangle visibleRect,
      int orientation) {
    // Find the cell at the trailing edge.  Return the distance to put
    // that cell at the leading edge.
    int trailingRow = getTrailingRow(visibleRect);
    int trailingCol = getTrailingCol(visibleRect);

    Rectangle cellRect;
    boolean cellFillsVis;

    int cellLeadingEdge;
    int cellTrailingEdge;
    int newLeadingEdge;
    int visibleLeadingEdge = leadingEdge(visibleRect, orientation);

    // If we couldn't find trailing cell, just return the size of the
    // visibleRect.  Note that, for instance, we don't need the
    // trailingCol to proceed if we're scrolling vertically, because
    // cellRect will still fill in the required dimensions.  This would
    // happen if we're scrolling vertically, and the table is not wide
    // enough to fill the visibleRect.
    if (orientation == SwingConstants.VERTICAL && trailingRow < 0) {
      return visibleRect.height;
    } else if (orientation == SwingConstants.HORIZONTAL && trailingCol < 0) {
      return visibleRect.width;
    }
    cellRect = getCellRect(trailingRow, trailingCol, true);
    cellLeadingEdge = leadingEdge(cellRect, orientation);
    cellTrailingEdge = trailingEdge(cellRect, orientation);

    if (orientation == SwingConstants.VERTICAL ||
        getComponentOrientation().isLeftToRight()) {
      cellFillsVis = cellLeadingEdge <= visibleLeadingEdge;
    } else { // Horizontal, right-to-left
      cellFillsVis = cellLeadingEdge >= visibleLeadingEdge;
    }

    if (cellFillsVis) {
      // The visibleRect contains a single large cell.  Scroll to the end
      // of this cell, so the following cell is the first cell.
      newLeadingEdge = cellTrailingEdge;
    } else if (cellTrailingEdge == trailingEdge(visibleRect, orientation)) {
      // The trailing cell happens to end right at the end of the
      // visibleRect.  Again, scroll to the beginning of the next cell.
      newLeadingEdge = cellTrailingEdge;
    } else {
      // Common case: the trailing cell is partially visible, and isn't
      // big enough to take up the entire visibleRect.  Scroll so it
      // becomes the leading cell.
      newLeadingEdge = cellLeadingEdge;
    }
    return Math.abs(newLeadingEdge - visibleLeadingEdge);
  }

  /*
     * Return the row at the top of the visibleRect
     *
     * May return -1
     */
  private int getLeadingRow(Rectangle visibleRect) {
    Point leadingPoint;

    if (getComponentOrientation().isLeftToRight()) {
      leadingPoint = new Point(visibleRect.x, visibleRect.y);
    } else {
      leadingPoint = new Point(visibleRect.x + visibleRect.width - 1,
          visibleRect.y);
    }
    return rowAtPoint(leadingPoint);
  }

  /*
     * Return the column at the leading edge of the visibleRect.
     *
     * May return -1
     */
  private int getLeadingCol(Rectangle visibleRect) {
    Point leadingPoint;

    if (getComponentOrientation().isLeftToRight()) {
      leadingPoint = new Point(visibleRect.x, visibleRect.y);
    } else {
      leadingPoint = new Point(visibleRect.x + visibleRect.width - 1,
          visibleRect.y);
    }
    return columnAtPoint(leadingPoint);
  }

  /*
     * Return the row at the bottom of the visibleRect.
     *
     * May return -1
     */
  private int getTrailingRow(Rectangle visibleRect) {
    Point trailingPoint;

    if (getComponentOrientation().isLeftToRight()) {
      trailingPoint = new Point(visibleRect.x,
          visibleRect.y + visibleRect.height - 1);
    } else {
      trailingPoint = new Point(visibleRect.x + visibleRect.width - 1,
          visibleRect.y + visibleRect.height - 1);
    }
    return rowAtPoint(trailingPoint);
  }

  /*
     * Return the column at the trailing edge of the visibleRect.
     *
     * May return -1
     */
  private int getTrailingCol(Rectangle visibleRect) {
    Point trailingPoint;

    if (getComponentOrientation().isLeftToRight()) {
      trailingPoint = new Point(visibleRect.x + visibleRect.width - 1,
          visibleRect.y);
    } else {
      trailingPoint = new Point(visibleRect.x, visibleRect.y);
    }
    return columnAtPoint(trailingPoint);
  }

  /*
     * Returns the leading edge ("beginning") of the given Rectangle.
     * For VERTICAL, this is the top, for left-to-right, the left side, and for
     * right-to-left, the right side.
     */
  private int leadingEdge(Rectangle rect, int orientation) {
    if (orientation == SwingConstants.VERTICAL) {
      return rect.y;
    } else if (getComponentOrientation().isLeftToRight()) {
      return rect.x;
    } else { // Horizontal, right-to-left
      return rect.x + rect.width;
    }
  }

  /*
     * Returns the trailing edge ("end") of the given Rectangle.
     * For VERTICAL, this is the bottom, for left-to-right, the right side, and
     * for right-to-left, the left side.
     */
  private int trailingEdge(Rectangle rect, int orientation) {
    if (orientation == SwingConstants.VERTICAL) {
      return rect.y + rect.height;
    } else if (getComponentOrientation().isLeftToRight()) {
      return rect.x + rect.width;
    } else { // Horizontal, right-to-left
      return rect.x;
    }
  }

  /**
   * Returns false if <code>autoResizeMode</code> is set to
   * <code>AUTO_RESIZE_OFF</code>, which indicates that the
   * width of the viewport does not determine the width
   * of the table.  Otherwise returns true.
   *
   * @return false if <code>autoResizeMode</code> is set to <code>AUTO_RESIZE_OFF</code>, otherwise
   * returns true
   * @see Scrollable#getScrollableTracksViewportWidth
   */
  public boolean getScrollableTracksViewportWidth() {
    return !(autoResizeMode == AUTO_RESIZE_OFF);
  }

  /**
   * Returns {@code false} to indicate that the height of the viewport does
   * not determine the height of the table, unless
   * {@code getFillsViewportHeight} is {@code true} and the preferred height
   * of the table is smaller than the viewport's height.
   *
   * @return {@code false} unless {@code getFillsViewportHeight} is {@code true} and the table needs
   * to be stretched to fill the viewport
   * @see Scrollable#getScrollableTracksViewportHeight
   * @see #setFillsViewportHeight
   * @see #getFillsViewportHeight
   */
  public boolean getScrollableTracksViewportHeight() {
    Container parent = SwingUtilities.getUnwrappedParent(this);
    return getFillsViewportHeight()
        && parent instanceof JViewport
        && parent.getHeight() > getPreferredSize().height;
  }

  /**
   * Sets whether or not this table is always made large enough
   * to fill the height of an enclosing viewport. If the preferred
   * height of the table is smaller than the viewport, then the table
   * will be stretched to fill the viewport. In other words, this
   * ensures the table is never smaller than the viewport.
   * The default for this property is {@code false}.
   *
   * @param fillsViewportHeight whether or not this table is always made large enough to fill the
   * height of an enclosing viewport
   * @beaninfo bound: true description: Whether or not this table is always made large enough to
   * fill the height of an enclosing viewport
   * @see #getFillsViewportHeight
   * @see #getScrollableTracksViewportHeight
   * @since 1.6
   */
  public void setFillsViewportHeight(boolean fillsViewportHeight) {
    boolean old = this.fillsViewportHeight;
    this.fillsViewportHeight = fillsViewportHeight;
    resizeAndRepaint();
    firePropertyChange("fillsViewportHeight", old, fillsViewportHeight);
  }

  /**
   * Returns whether or not this table is always made large enough
   * to fill the height of an enclosing viewport.
   *
   * @return whether or not this table is always made large enough to fill the height of an
   * enclosing viewport
   * @see #setFillsViewportHeight
   * @since 1.6
   */
  public boolean getFillsViewportHeight() {
    return fillsViewportHeight;
  }

//
// Protected Methods
//

  protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
      int condition, boolean pressed) {
    boolean retValue = super.processKeyBinding(ks, e, condition, pressed);

    // Start editing when a key is typed. UI classes can disable this behavior
    // by setting the client property JTable.autoStartsEdit to Boolean.FALSE.
    if (!retValue && condition == WHEN_ANCESTOR_OF_FOCUSED_COMPONENT &&
        isFocusOwner() &&
        !Boolean.FALSE.equals(getClientProperty("JTable.autoStartsEdit"))) {
      // We do not have a binding for the event.
      Component editorComponent = getEditorComponent();
      if (editorComponent == null) {
        // Only attempt to install the editor on a KEY_PRESSED,
        if (e == null || e.getID() != KeyEvent.KEY_PRESSED) {
          return false;
        }
        // Don't start when just a modifier is pressed
        int code = e.getKeyCode();
        if (code == KeyEvent.VK_SHIFT || code == KeyEvent.VK_CONTROL ||
            code == KeyEvent.VK_ALT) {
          return false;
        }
        // Try to install the editor
        int leadRow = getSelectionModel().getLeadSelectionIndex();
        int leadColumn = getColumnModel().getSelectionModel().
            getLeadSelectionIndex();
        if (leadRow != -1 && leadColumn != -1 && !isEditing()) {
          if (!editCellAt(leadRow, leadColumn, e)) {
            return false;
          }
        }
        editorComponent = getEditorComponent();
        if (editorComponent == null) {
          return false;
        }
      }
      // If the editorComponent is a JComponent, pass the event to it.
      if (editorComponent instanceof JComponent) {
        retValue = ((JComponent) editorComponent).processKeyBinding
            (ks, e, WHEN_FOCUSED, pressed);
        // If we have started an editor as a result of the user
        // pressing a key and the surrendersFocusOnKeystroke property
        // is true, give the focus to the new editor.
        if (getSurrendersFocusOnKeystroke()) {
          editorComponent.requestFocus();
        }
      }
    }
    return retValue;
  }

  /**
   * Creates default cell renderers for objects, numbers, doubles, dates,
   * booleans, and icons.
   *
   * @see javax.swing.table.DefaultTableCellRenderer
   */
  protected void createDefaultRenderers() {
    defaultRenderersByColumnClass = new UIDefaults(8, 0.75f);

    // Objects
    defaultRenderersByColumnClass
        .put(Object.class, (UIDefaults.LazyValue) t -> new DefaultTableCellRenderer.UIResource());

    // Numbers
    defaultRenderersByColumnClass
        .put(Number.class, (UIDefaults.LazyValue) t -> new NumberRenderer());

    // Doubles and Floats
    defaultRenderersByColumnClass
        .put(Float.class, (UIDefaults.LazyValue) t -> new DoubleRenderer());
    defaultRenderersByColumnClass
        .put(Double.class, (UIDefaults.LazyValue) t -> new DoubleRenderer());

    // Dates
    defaultRenderersByColumnClass.put(Date.class, (UIDefaults.LazyValue) t -> new DateRenderer());

    // Icons and ImageIcons
    defaultRenderersByColumnClass.put(Icon.class, (UIDefaults.LazyValue) t -> new IconRenderer());
    defaultRenderersByColumnClass
        .put(ImageIcon.class, (UIDefaults.LazyValue) t -> new IconRenderer());

    // Booleans
    defaultRenderersByColumnClass
        .put(Boolean.class, (UIDefaults.LazyValue) t -> new BooleanRenderer());
  }

  /**
   * Default Renderers
   **/
  static class NumberRenderer extends DefaultTableCellRenderer.UIResource {

    public NumberRenderer() {
      super();
      setHorizontalAlignment(JLabel.RIGHT);
    }
  }

  static class DoubleRenderer extends NumberRenderer {

    NumberFormat formatter;

    public DoubleRenderer() {
      super();
    }

    public void setValue(Object value) {
      if (formatter == null) {
        formatter = NumberFormat.getInstance();
      }
      setText((value == null) ? "" : formatter.format(value));
    }
  }

  static class DateRenderer extends DefaultTableCellRenderer.UIResource {

    DateFormat formatter;

    public DateRenderer() {
      super();
    }

    public void setValue(Object value) {
      if (formatter == null) {
        formatter = DateFormat.getDateInstance();
      }
      setText((value == null) ? "" : formatter.format(value));
    }
  }

  static class IconRenderer extends DefaultTableCellRenderer.UIResource {

    public IconRenderer() {
      super();
      setHorizontalAlignment(JLabel.CENTER);
    }

    public void setValue(Object value) {
      setIcon((value instanceof Icon) ? (Icon) value : null);
    }
  }


  static class BooleanRenderer extends JCheckBox implements TableCellRenderer, UIResource {

    private static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);

    public BooleanRenderer() {
      super();
      setHorizontalAlignment(JLabel.CENTER);
      setBorderPainted(true);
    }

    public Component getTableCellRendererComponent(JTable table, Object value,
        boolean isSelected, boolean hasFocus, int row, int column) {
      if (isSelected) {
        setForeground(table.getSelectionForeground());
        super.setBackground(table.getSelectionBackground());
      } else {
        setForeground(table.getForeground());
        setBackground(table.getBackground());
      }
      setSelected((value != null && ((Boolean) value).booleanValue()));

      if (hasFocus) {
        setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
      } else {
        setBorder(noFocusBorder);
      }

      return this;
    }
  }

  /**
   * Creates default cell editors for objects, numbers, and boolean values.
   *
   * @see DefaultCellEditor
   */
  protected void createDefaultEditors() {
    defaultEditorsByColumnClass = new UIDefaults(3, 0.75f);

    // Objects
    defaultEditorsByColumnClass.put(Object.class, (UIDefaults.LazyValue) t -> new GenericEditor());

    // Numbers
    defaultEditorsByColumnClass.put(Number.class, (UIDefaults.LazyValue) t -> new NumberEditor());

    // Booleans
    defaultEditorsByColumnClass.put(Boolean.class, (UIDefaults.LazyValue) t -> new BooleanEditor());
  }

  /**
   * Default Editors
   */
  static class GenericEditor extends DefaultCellEditor {

    Class[] argTypes = new Class[]{String.class};
    java.lang.reflect.Constructor constructor;
    Object value;

    public GenericEditor() {
      super(new JTextField());
      getComponent().setName("Table.editor");
    }

    public boolean stopCellEditing() {
      String s = (String) super.getCellEditorValue();
      // Here we are dealing with the case where a user
      // has deleted the string value in a cell, possibly
      // after a failed validation. Return null, so that
      // they have the option to replace the value with
      // null or use escape to restore the original.
      // For Strings, return "" for backward compatibility.
      try {
        if ("".equals(s)) {
          if (constructor.getDeclaringClass() == String.class) {
            value = s;
          }
          return super.stopCellEditing();
        }

        SwingUtilities2.checkAccess(constructor.getModifiers());
        value = constructor.newInstance(new Object[]{s});
      } catch (Exception e) {
        ((JComponent) getComponent()).setBorder(new LineBorder(Color.red));
        return false;
      }
      return super.stopCellEditing();
    }

    public Component getTableCellEditorComponent(JTable table, Object value,
        boolean isSelected,
        int row, int column) {
      this.value = null;
      ((JComponent) getComponent()).setBorder(new LineBorder(Color.black));
      try {
        Class<?> type = table.getColumnClass(column);
        // Since our obligation is to produce a value which is
        // assignable for the required type it is OK to use the
        // String constructor for columns which are declared
        // to contain Objects. A String is an Object.
        if (type == Object.class) {
          type = String.class;
        }
        ReflectUtil.checkPackageAccess(type);
        SwingUtilities2.checkAccess(type.getModifiers());
        constructor = type.getConstructor(argTypes);
      } catch (Exception e) {
        return null;
      }
      return super.getTableCellEditorComponent(table, value, isSelected, row, column);
    }

    public Object getCellEditorValue() {
      return value;
    }
  }

  static class NumberEditor extends GenericEditor {

    public NumberEditor() {
      ((JTextField) getComponent()).setHorizontalAlignment(JTextField.RIGHT);
    }
  }

  static class BooleanEditor extends DefaultCellEditor {

    public BooleanEditor() {
      super(new JCheckBox());
      JCheckBox checkBox = (JCheckBox) getComponent();
      checkBox.setHorizontalAlignment(JCheckBox.CENTER);
    }
  }

  /**
   * Initializes table properties to their default values.
   */
  protected void initializeLocalVars() {
    updateSelectionOnSort = true;
    setOpaque(true);
    createDefaultRenderers();
    createDefaultEditors();

    setTableHeader(createDefaultTableHeader());

    setShowGrid(true);
    setAutoResizeMode(AUTO_RESIZE_SUBSEQUENT_COLUMNS);
    setRowHeight(16);
    isRowHeightSet = false;
    setRowMargin(1);
    setRowSelectionAllowed(true);
    setCellEditor(null);
    setEditingColumn(-1);
    setEditingRow(-1);
    setSurrendersFocusOnKeystroke(false);
    setPreferredScrollableViewportSize(new Dimension(450, 400));

    // I'm registered to do tool tips so we can draw tips for the renderers
    ToolTipManager toolTipManager = ToolTipManager.sharedInstance();
    toolTipManager.registerComponent(this);

    setAutoscrolls(true);
  }

  /**
   * Returns the default table model object, which is
   * a <code>DefaultTableModel</code>.  A subclass can override this
   * method to return a different table model object.
   *
   * @return the default table model object
   * @see javax.swing.table.DefaultTableModel
   */
  protected TableModel createDefaultDataModel() {
    return new DefaultTableModel();
  }

  /**
   * Returns the default column model object, which is
   * a <code>DefaultTableColumnModel</code>.  A subclass can override this
   * method to return a different column model object.
   *
   * @return the default column model object
   * @see javax.swing.table.DefaultTableColumnModel
   */
  protected TableColumnModel createDefaultColumnModel() {
    return new DefaultTableColumnModel();
  }

  /**
   * Returns the default selection model object, which is
   * a <code>DefaultListSelectionModel</code>.  A subclass can override this
   * method to return a different selection model object.
   *
   * @return the default selection model object
   * @see javax.swing.DefaultListSelectionModel
   */
  protected ListSelectionModel createDefaultSelectionModel() {
    return new DefaultListSelectionModel();
  }

  /**
   * Returns the default table header object, which is
   * a <code>JTableHeader</code>.  A subclass can override this
   * method to return a different table header object.
   *
   * @return the default table header object
   * @see javax.swing.table.JTableHeader
   */
  protected JTableHeader createDefaultTableHeader() {
    return new JTableHeader(columnModel);
  }

  /**
   * Equivalent to <code>revalidate</code> followed by <code>repaint</code>.
   */
  protected void resizeAndRepaint() {
    revalidate();
    repaint();
  }

  /**
   * Returns the active cell editor, which is {@code null} if the table
   * is not currently editing.
   *
   * @return the {@code TableCellEditor} that does the editing, or {@code null} if the table is not
   * currently editing.
   * @see #cellEditor
   * @see #getCellEditor(int, int)
   */
  public TableCellEditor getCellEditor() {
    return cellEditor;
  }

  /**
   * Sets the active cell editor.
   *
   * @param anEditor the active cell editor
   * @beaninfo bound: true description: The table's active cell editor.
   * @see #cellEditor
   */
  public void setCellEditor(TableCellEditor anEditor) {
    TableCellEditor oldEditor = cellEditor;
    cellEditor = anEditor;
    firePropertyChange("tableCellEditor", oldEditor, anEditor);
  }

  /**
   * Sets the <code>editingColumn</code> variable.
   *
   * @param aColumn the column of the cell to be edited
   * @see #editingColumn
   */
  public void setEditingColumn(int aColumn) {
    editingColumn = aColumn;
  }

  /**
   * Sets the <code>editingRow</code> variable.
   *
   * @param aRow the row of the cell to be edited
   * @see #editingRow
   */
  public void setEditingRow(int aRow) {
    editingRow = aRow;
  }

  /**
   * Returns an appropriate renderer for the cell specified by this row and
   * column. If the <code>TableColumn</code> for this column has a non-null
   * renderer, returns that.  If not, finds the class of the data in
   * this column (using <code>getColumnClass</code>)
   * and returns the default renderer for this type of data.
   * <p>
   * <b>Note:</b>
   * Throughout the table package, the internal implementations always
   * use this method to provide renderers so that this default behavior
   * can be safely overridden by a subclass.
   *
   * @param row the row of the cell to render, where 0 is the first row
   * @param column the column of the cell to render, where 0 is the first column
   * @return the assigned renderer; if <code>null</code> returns the default renderer for this type
   * of object
   * @see javax.swing.table.DefaultTableCellRenderer
   * @see javax.swing.table.TableColumn#setCellRenderer
   * @see #setDefaultRenderer
   */
  public TableCellRenderer getCellRenderer(int row, int column) {
    TableColumn tableColumn = getColumnModel().getColumn(column);
    TableCellRenderer renderer = tableColumn.getCellRenderer();
    if (renderer == null) {
      renderer = getDefaultRenderer(getColumnClass(column));
    }
    return renderer;
  }

  /**
   * Prepares the renderer by querying the data model for the
   * value and selection state
   * of the cell at <code>row</code>, <code>column</code>.
   * Returns the component (may be a <code>Component</code>
   * or a <code>JComponent</code>) under the event location.
   * <p>
   * During a printing operation, this method will configure the
   * renderer without indicating selection or focus, to prevent
   * them from appearing in the printed output. To do other
   * customizations based on whether or not the table is being
   * printed, you can check the value of
   * {@link javax.swing.JComponent#isPaintingForPrint()}, either here
   * or within custom renderers.
   * <p>
   * <b>Note:</b>
   * Throughout the table package, the internal implementations always
   * use this method to prepare renderers so that this default behavior
   * can be safely overridden by a subclass.
   *
   * @param renderer the <code>TableCellRenderer</code> to prepare
   * @param row the row of the cell to render, where 0 is the first row
   * @param column the column of the cell to render, where 0 is the first column
   * @return the <code>Component</code> under the event location
   */
  public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
    Object value = getValueAt(row, column);

    boolean isSelected = false;
    boolean hasFocus = false;

    // Only indicate the selection and focused cell if not printing
    if (!isPaintingForPrint()) {
      isSelected = isCellSelected(row, column);

      boolean rowIsLead =
          (selectionModel.getLeadSelectionIndex() == row);
      boolean colIsLead =
          (columnModel.getSelectionModel().getLeadSelectionIndex() == column);

      hasFocus = (rowIsLead && colIsLead) && isFocusOwner();
    }

    return renderer.getTableCellRendererComponent(this, value,
        isSelected, hasFocus,
        row, column);
  }

  /**
   * Returns an appropriate editor for the cell specified by
   * <code>row</code> and <code>column</code>. If the
   * <code>TableColumn</code> for this column has a non-null editor,
   * returns that.  If not, finds the class of the data in this
   * column (using <code>getColumnClass</code>)
   * and returns the default editor for this type of data.
   * <p>
   * <b>Note:</b>
   * Throughout the table package, the internal implementations always
   * use this method to provide editors so that this default behavior
   * can be safely overridden by a subclass.
   *
   * @param row the row of the cell to edit, where 0 is the first row
   * @param column the column of the cell to edit, where 0 is the first column
   * @return the editor for this cell; if <code>null</code> return the default editor for this type
   * of cell
   * @see DefaultCellEditor
   */
  public TableCellEditor getCellEditor(int row, int column) {
    TableColumn tableColumn = getColumnModel().getColumn(column);
    TableCellEditor editor = tableColumn.getCellEditor();
    if (editor == null) {
      editor = getDefaultEditor(getColumnClass(column));
    }
    return editor;
  }


  /**
   * Prepares the editor by querying the data model for the value and
   * selection state of the cell at <code>row</code>, <code>column</code>.
   * <p>
   * <b>Note:</b>
   * Throughout the table package, the internal implementations always
   * use this method to prepare editors so that this default behavior
   * can be safely overridden by a subclass.
   *
   * @param editor the <code>TableCellEditor</code> to set up
   * @param row the row of the cell to edit, where 0 is the first row
   * @param column the column of the cell to edit, where 0 is the first column
   * @return the <code>Component</code> being edited
   */
  public Component prepareEditor(TableCellEditor editor, int row, int column) {
    Object value = getValueAt(row, column);
    boolean isSelected = isCellSelected(row, column);
    Component comp = editor.getTableCellEditorComponent(this, value, isSelected,
        row, column);
    if (comp instanceof JComponent) {
      JComponent jComp = (JComponent) comp;
      if (jComp.getNextFocusableComponent() == null) {
        jComp.setNextFocusableComponent(this);
      }
    }
    return comp;
  }

  /**
   * Discards the editor object and frees the real estate it used for
   * cell rendering.
   */
  public void removeEditor() {
    KeyboardFocusManager.getCurrentKeyboardFocusManager().
        removePropertyChangeListener("permanentFocusOwner", editorRemover);
    editorRemover = null;

    TableCellEditor editor = getCellEditor();
    if (editor != null) {
      editor.removeCellEditorListener(this);
      if (editorComp != null) {
        Component focusOwner =
            KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
        boolean isFocusOwnerInTheTable = focusOwner != null ?
            SwingUtilities.isDescendingFrom(focusOwner, this) : false;
        remove(editorComp);
        if (isFocusOwnerInTheTable) {
          requestFocusInWindow();
        }
      }

      Rectangle cellRect = getCellRect(editingRow, editingColumn, false);

      setCellEditor(null);
      setEditingColumn(-1);
      setEditingRow(-1);
      editorComp = null;

      repaint(cellRect);
    }
  }

//
// Serialization
//

  /**
   * See readObject() and writeObject() in JComponent for more
   * information about serialization in Swing.
   */
  private void writeObject(ObjectOutputStream s) throws IOException {
    s.defaultWriteObject();
    if (getUIClassID().equals(uiClassID)) {
      byte count = JComponent.getWriteObjCounter(this);
      JComponent.setWriteObjCounter(this, --count);
      if (count == 0 && ui != null) {
        ui.installUI(this);
      }
    }
  }

  private void readObject(ObjectInputStream s)
      throws IOException, ClassNotFoundException {
    s.defaultReadObject();
    if ((ui != null) && (getUIClassID().equals(uiClassID))) {
      ui.installUI(this);
    }
    createDefaultRenderers();
    createDefaultEditors();

    // If ToolTipText != null, then the tooltip has already been
    // registered by JComponent.readObject() and we don't want
    // to re-register here
    if (getToolTipText() == null) {
      ToolTipManager.sharedInstance().registerComponent(this);
    }
  }

  /* Called from the JComponent's EnableSerializationFocusListener to
     * do any Swing-specific pre-serialization configuration.
     */
  void compWriteObjectNotify() {
    super.compWriteObjectNotify();
    // If ToolTipText != null, then the tooltip has already been
    // unregistered by JComponent.compWriteObjectNotify()
    if (getToolTipText() == null) {
      ToolTipManager.sharedInstance().unregisterComponent(this);
    }
  }

  /**
   * Returns a string representation of this table. This method
   * is intended to be used only for debugging purposes, and the
   * content and format of the returned string may vary between
   * implementations. The returned string may be empty but may not
   * be <code>null</code>.
   *
   * @return a string representation of this table
   */
  protected String paramString() {
    String gridColorString = (gridColor != null ?
        gridColor.toString() : "");
    String showHorizontalLinesString = (showHorizontalLines ?
        "true" : "false");
    String showVerticalLinesString = (showVerticalLines ?
        "true" : "false");
    String autoResizeModeString;
    if (autoResizeMode == AUTO_RESIZE_OFF) {
      autoResizeModeString = "AUTO_RESIZE_OFF";
    } else if (autoResizeMode == AUTO_RESIZE_NEXT_COLUMN) {
      autoResizeModeString = "AUTO_RESIZE_NEXT_COLUMN";
    } else if (autoResizeMode == AUTO_RESIZE_SUBSEQUENT_COLUMNS) {
      autoResizeModeString = "AUTO_RESIZE_SUBSEQUENT_COLUMNS";
    } else if (autoResizeMode == AUTO_RESIZE_LAST_COLUMN) {
      autoResizeModeString = "AUTO_RESIZE_LAST_COLUMN";
    } else if (autoResizeMode == AUTO_RESIZE_ALL_COLUMNS) {
      autoResizeModeString = "AUTO_RESIZE_ALL_COLUMNS";
    } else {
      autoResizeModeString = "";
    }
    String autoCreateColumnsFromModelString = (autoCreateColumnsFromModel ?
        "true" : "false");
    String preferredViewportSizeString = (preferredViewportSize != null ?
        preferredViewportSize.toString()
        : "");
    String rowSelectionAllowedString = (rowSelectionAllowed ?
        "true" : "false");
    String cellSelectionEnabledString = (cellSelectionEnabled ?
        "true" : "false");
    String selectionForegroundString = (selectionForeground != null ?
        selectionForeground.toString() :
        "");
    String selectionBackgroundString = (selectionBackground != null ?
        selectionBackground.toString() :
        "");

    return super.paramString() +
        ",autoCreateColumnsFromModel=" + autoCreateColumnsFromModelString +
        ",autoResizeMode=" + autoResizeModeString +
        ",cellSelectionEnabled=" + cellSelectionEnabledString +
        ",editingColumn=" + editingColumn +
        ",editingRow=" + editingRow +
        ",gridColor=" + gridColorString +
        ",preferredViewportSize=" + preferredViewportSizeString +
        ",rowHeight=" + rowHeight +
        ",rowMargin=" + rowMargin +
        ",rowSelectionAllowed=" + rowSelectionAllowedString +
        ",selectionBackground=" + selectionBackgroundString +
        ",selectionForeground=" + selectionForegroundString +
        ",showHorizontalLines=" + showHorizontalLinesString +
        ",showVerticalLines=" + showVerticalLinesString;
  }

  // This class tracks changes in the keyboard focus state. It is used
  // when the JTable is editing to determine when to cancel the edit.
  // If focus switches to a component outside of the jtable, but in the
  // same window, this will cancel editing.
  class CellEditorRemover implements PropertyChangeListener {

    KeyboardFocusManager focusManager;

    public CellEditorRemover(KeyboardFocusManager fm) {
      this.focusManager = fm;
    }

    public void propertyChange(PropertyChangeEvent ev) {
      if (!isEditing() || getClientProperty("terminateEditOnFocusLost") != Boolean.TRUE) {
        return;
      }

      Component c = focusManager.getPermanentFocusOwner();
      while (c != null) {
        if (c == JTable.this) {
          // focus remains inside the table
          return;
        } else if ((c instanceof Window) ||
            (c instanceof Applet && c.getParent() == null)) {
          if (c == SwingUtilities.getRoot(JTable.this)) {
            if (!getCellEditor().stopCellEditing()) {
              getCellEditor().cancelCellEditing();
            }
          }
          break;
        }
        c = c.getParent();
      }
    }
  }

/////////////////
// Printing Support
/////////////////

  /**
   * A convenience method that displays a printing dialog, and then prints
   * this <code>JTable</code> in mode <code>PrintMode.FIT_WIDTH</code>,
   * with no header or footer text. A modal progress dialog, with an abort
   * option, will be shown for the duration of printing.
   * <p>
   * Note: In headless mode, no dialogs are shown and printing
   * occurs on the default printer.
   *
   * @return true, unless printing is cancelled by the user
   * @throws SecurityException if this thread is not allowed to initiate a print job request
   * @throws PrinterException if an error in the print system causes the job to be aborted
   * @see #print(JTable.PrintMode, MessageFormat, MessageFormat, boolean, PrintRequestAttributeSet,
   * boolean, PrintService)
   * @see #getPrintable
   * @since 1.5
   */
  public boolean print() throws PrinterException {

    return print(PrintMode.FIT_WIDTH);
  }

  /**
   * A convenience method that displays a printing dialog, and then prints
   * this <code>JTable</code> in the given printing mode,
   * with no header or footer text. A modal progress dialog, with an abort
   * option, will be shown for the duration of printing.
   * <p>
   * Note: In headless mode, no dialogs are shown and printing
   * occurs on the default printer.
   *
   * @param printMode the printing mode that the printable should use
   * @return true, unless printing is cancelled by the user
   * @throws SecurityException if this thread is not allowed to initiate a print job request
   * @throws PrinterException if an error in the print system causes the job to be aborted
   * @see #print(JTable.PrintMode, MessageFormat, MessageFormat, boolean, PrintRequestAttributeSet,
   * boolean, PrintService)
   * @see #getPrintable
   * @since 1.5
   */
  public boolean print(PrintMode printMode) throws PrinterException {

    return print(printMode, null, null);
  }

  /**
   * A convenience method that displays a printing dialog, and then prints
   * this <code>JTable</code> in the given printing mode,
   * with the specified header and footer text. A modal progress dialog,
   * with an abort option, will be shown for the duration of printing.
   * <p>
   * Note: In headless mode, no dialogs are shown and printing
   * occurs on the default printer.
   *
   * @param printMode the printing mode that the printable should use
   * @param headerFormat a <code>MessageFormat</code> specifying the text to be used in printing a
   * header, or null for none
   * @param footerFormat a <code>MessageFormat</code> specifying the text to be used in printing a
   * footer, or null for none
   * @return true, unless printing is cancelled by the user
   * @throws SecurityException if this thread is not allowed to initiate a print job request
   * @throws PrinterException if an error in the print system causes the job to be aborted
   * @see #print(JTable.PrintMode, MessageFormat, MessageFormat, boolean, PrintRequestAttributeSet,
   * boolean, PrintService)
   * @see #getPrintable
   * @since 1.5
   */
  public boolean print(PrintMode printMode,
      MessageFormat headerFormat,
      MessageFormat footerFormat) throws PrinterException {

    boolean showDialogs = !GraphicsEnvironment.isHeadless();
    return print(printMode, headerFormat, footerFormat,
        showDialogs, null, showDialogs);
  }

  /**
   * Prints this table, as specified by the fully featured
   * {@link #print(JTable.PrintMode, MessageFormat, MessageFormat,
   * boolean, PrintRequestAttributeSet, boolean, PrintService) print}
   * method, with the default printer specified as the print service.
   *
   * @param printMode the printing mode that the printable should use
   * @param headerFormat a <code>MessageFormat</code> specifying the text to be used in printing a
   * header, or <code>null</code> for none
   * @param footerFormat a <code>MessageFormat</code> specifying the text to be used in printing a
   * footer, or <code>null</code> for none
   * @param showPrintDialog whether or not to display a print dialog
   * @param attr a <code>PrintRequestAttributeSet</code> specifying any printing attributes, or
   * <code>null</code> for none
   * @param interactive whether or not to print in an interactive mode
   * @return true, unless printing is cancelled by the user
   * @throws HeadlessException if the method is asked to show a printing dialog or run
   * interactively, and <code>GraphicsEnvironment.isHeadless</code> returns <code>true</code>
   * @throws SecurityException if this thread is not allowed to initiate a print job request
   * @throws PrinterException if an error in the print system causes the job to be aborted
   * @see #print(JTable.PrintMode, MessageFormat, MessageFormat, boolean, PrintRequestAttributeSet,
   * boolean, PrintService)
   * @see #getPrintable
   * @since 1.5
   */
  public boolean print(PrintMode printMode,
      MessageFormat headerFormat,
      MessageFormat footerFormat,
      boolean showPrintDialog,
      PrintRequestAttributeSet attr,
      boolean interactive) throws PrinterException,
      HeadlessException {

    return print(printMode,
        headerFormat,
        footerFormat,
        showPrintDialog,
        attr,
        interactive,
        null);
  }

  /**
   * Prints this <code>JTable</code>. Takes steps that the majority of
   * developers would take in order to print a <code>JTable</code>.
   * In short, it prepares the table, calls <code>getPrintable</code> to
   * fetch an appropriate <code>Printable</code>, and then sends it to the
   * printer.
   * <p>
   * A <code>boolean</code> parameter allows you to specify whether or not
   * a printing dialog is displayed to the user. When it is, the user may
   * use the dialog to change the destination printer or printing attributes,
   * or even to cancel the print. Another two parameters allow for a
   * <code>PrintService</code> and printing attributes to be specified.
   * These parameters can be used either to provide initial values for the
   * print dialog, or to specify values when the dialog is not shown.
   * <p>
   * A second <code>boolean</code> parameter allows you to specify whether
   * or not to perform printing in an interactive mode. If <code>true</code>,
   * a modal progress dialog, with an abort option, is displayed for the
   * duration of printing . This dialog also prevents any user action which
   * may affect the table. However, it can not prevent the table from being
   * modified by code (for example, another thread that posts updates using
   * <code>SwingUtilities.invokeLater</code>). It is therefore the
   * responsibility of the developer to ensure that no other code modifies
   * the table in any way during printing (invalid modifications include
   * changes in: size, renderers, or underlying data). Printing behavior is
   * undefined when the table is changed during printing.
   * <p>
   * If <code>false</code> is specified for this parameter, no dialog will
   * be displayed and printing will begin immediately on the event-dispatch
   * thread. This blocks any other events, including repaints, from being
   * processed until printing is complete. Although this effectively prevents
   * the table from being changed, it doesn't provide a good user experience.
   * For this reason, specifying <code>false</code> is only recommended when
   * printing from an application with no visible GUI.
   * <p>
   * Note: Attempting to show the printing dialog or run interactively, while
   * in headless mode, will result in a <code>HeadlessException</code>.
   * <p>
   * Before fetching the printable, this method will gracefully terminate
   * editing, if necessary, to prevent an editor from showing in the printed
   * result. Additionally, <code>JTable</code> will prepare its renderers
   * during printing such that selection and focus are not indicated.
   * As far as customizing further how the table looks in the printout,
   * developers can provide custom renderers or paint code that conditionalize
   * on the value of {@link javax.swing.JComponent#isPaintingForPrint()}.
   * <p>
   * See {@link #getPrintable} for more description on how the table is
   * printed.
   *
   * @param printMode the printing mode that the printable should use
   * @param headerFormat a <code>MessageFormat</code> specifying the text to be used in printing a
   * header, or <code>null</code> for none
   * @param footerFormat a <code>MessageFormat</code> specifying the text to be used in printing a
   * footer, or <code>null</code> for none
   * @param showPrintDialog whether or not to display a print dialog
   * @param attr a <code>PrintRequestAttributeSet</code> specifying any printing attributes, or
   * <code>null</code> for none
   * @param interactive whether or not to print in an interactive mode
   * @param service the destination <code>PrintService</code>, or <code>null</code> to use the
   * default printer
   * @return true, unless printing is cancelled by the user
   * @throws HeadlessException if the method is asked to show a printing dialog or run
   * interactively, and <code>GraphicsEnvironment.isHeadless</code> returns <code>true</code>
   * @throws SecurityException if a security manager exists and its {@link
   * java.lang.SecurityManager#checkPrintJobAccess} method disallows this thread from creating a
   * print job request
   * @throws PrinterException if an error in the print system causes the job to be aborted
   * @see #getPrintable
   * @see java.awt.GraphicsEnvironment#isHeadless
   * @since 1.6
   */
  public boolean print(PrintMode printMode,
      MessageFormat headerFormat,
      MessageFormat footerFormat,
      boolean showPrintDialog,
      PrintRequestAttributeSet attr,
      boolean interactive,
      PrintService service) throws PrinterException,
      HeadlessException {

    // complain early if an invalid parameter is specified for headless mode
    boolean isHeadless = GraphicsEnvironment.isHeadless();
    if (isHeadless) {
      if (showPrintDialog) {
        throw new HeadlessException("Can't show print dialog.");
      }

      if (interactive) {
        throw new HeadlessException("Can't run interactively.");
      }
    }

    // Get a PrinterJob.
    // Do this before anything with side-effects since it may throw a
    // security exception - in which case we don't want to do anything else.
    final PrinterJob job = PrinterJob.getPrinterJob();

    if (isEditing()) {
      // try to stop cell editing, and failing that, cancel it
      if (!getCellEditor().stopCellEditing()) {
        getCellEditor().cancelCellEditing();
      }
    }

    if (attr == null) {
      attr = new HashPrintRequestAttributeSet();
    }

    final PrintingStatus printingStatus;

    // fetch the Printable
    Printable printable =
        getPrintable(printMode, headerFormat, footerFormat);

    if (interactive) {
      // wrap the Printable so that we can print on another thread
      printable = new ThreadSafePrintable(printable);
      printingStatus = PrintingStatus.createPrintingStatus(this, job);
      printable = printingStatus.createNotificationPrintable(printable);
    } else {
      // to please compiler
      printingStatus = null;
    }

    // set the printable on the PrinterJob
    job.setPrintable(printable);

    // if specified, set the PrintService on the PrinterJob
    if (service != null) {
      job.setPrintService(service);
    }

    // if requested, show the print dialog
    if (showPrintDialog && !job.printDialog(attr)) {
      // the user cancelled the print dialog
      return false;
    }

    // if not interactive, just print on this thread (no dialog)
    if (!interactive) {
      // do the printing
      job.print(attr);

      // we're done
      return true;
    }

    // make sure this is clear since we'll check it after
    printError = null;

    // to synchronize on
    final Object lock = new Object();

    // copied so we can access from the inner class
    final PrintRequestAttributeSet copyAttr = attr;

    // this runnable will be used to do the printing
    // (and save any throwables) on another thread
    Runnable runnable = new Runnable() {
      public void run() {
        try {
          // do the printing
          job.print(copyAttr);
        } catch (Throwable t) {
          // save any Throwable to be rethrown
          synchronized (lock) {
            printError = t;
          }
        } finally {
          // we're finished - hide the dialog
          printingStatus.dispose();
        }
      }
    };

    // start printing on another thread
    Thread th = new Thread(runnable);
    th.start();

    printingStatus.showModal(true);

    // look for any error that the printing may have generated
    Throwable pe;
    synchronized (lock) {
      pe = printError;
      printError = null;
    }

    // check the type of error and handle it
    if (pe != null) {
      // a subclass of PrinterException meaning the job was aborted,
      // in this case, by the user
      if (pe instanceof PrinterAbortException) {
        return false;
      } else if (pe instanceof PrinterException) {
        throw (PrinterException) pe;
      } else if (pe instanceof RuntimeException) {
        throw (RuntimeException) pe;
      } else if (pe instanceof Error) {
        throw (Error) pe;
      }

      // can not happen
      throw new AssertionError(pe);
    }

    return true;
  }

  /**
   * Return a <code>Printable</code> for use in printing this JTable.
   * <p>
   * This method is meant for those wishing to customize the default
   * <code>Printable</code> implementation used by <code>JTable</code>'s
   * <code>print</code> methods. Developers wanting simply to print the table
   * should use one of those methods directly.
   * <p>
   * The <code>Printable</code> can be requested in one of two printing modes.
   * In both modes, it spreads table rows naturally in sequence across
   * multiple pages, fitting as many rows as possible per page.
   * <code>PrintMode.NORMAL</code> specifies that the table be
   * printed at its current size. In this mode, there may be a need to spread
   * columns across pages in a similar manner to that of the rows. When the
   * need arises, columns are distributed in an order consistent with the
   * table's <code>ComponentOrientation</code>.
   * <code>PrintMode.FIT_WIDTH</code> specifies that the output be
   * scaled smaller, if necessary, to fit the table's entire width
   * (and thereby all columns) on each page. Width and height are scaled
   * equally, maintaining the aspect ratio of the output.
   * <p>
   * The <code>Printable</code> heads the portion of table on each page
   * with the appropriate section from the table's <code>JTableHeader</code>,
   * if it has one.
   * <p>
   * Header and footer text can be added to the output by providing
   * <code>MessageFormat</code> arguments. The printing code requests
   * Strings from the formats, providing a single item which may be included
   * in the formatted string: an <code>Integer</code> representing the current
   * page number.
   * <p>
   * You are encouraged to read the documentation for
   * <code>MessageFormat</code> as some characters, such as single-quote,
   * are special and need to be escaped.
   * <p>
   * Here's an example of creating a <code>MessageFormat</code> that can be
   * used to print "Duke's Table: Page - " and the current page number:
   *
   * <pre>
   *     // notice the escaping of the single quote
   *     // notice how the page number is included with "{0}"
   *     MessageFormat format = new MessageFormat("Duke''s Table: Page - {0}");
   * </pre>
   * <p>
   * The <code>Printable</code> constrains what it draws to the printable
   * area of each page that it prints. Under certain circumstances, it may
   * find it impossible to fit all of a page's content into that area. In
   * these cases the output may be clipped, but the implementation
   * makes an effort to do something reasonable. Here are a few situations
   * where this is known to occur, and how they may be handled by this
   * particular implementation:
   * <ul>
   * <li>In any mode, when the header or footer text is too wide to fit
   * completely in the printable area -- print as much of the text as
   * possible starting from the beginning, as determined by the table's
   * <code>ComponentOrientation</code>.
   * <li>In any mode, when a row is too tall to fit in the
   * printable area -- print the upper-most portion of the row
   * and paint no lower border on the table.
   * <li>In <code>PrintMode.NORMAL</code> when a column
   * is too wide to fit in the printable area -- print the center
   * portion of the column and leave the left and right borders
   * off the table.
   * </ul>
   * <p>
   * It is entirely valid for this <code>Printable</code> to be wrapped
   * inside another in order to create complex reports and documents. You may
   * even request that different pages be rendered into different sized
   * printable areas. The implementation must be prepared to handle this
   * (possibly by doing its layout calculations on the fly). However,
   * providing different heights to each page will likely not work well
   * with <code>PrintMode.NORMAL</code> when it has to spread columns
   * across pages.
   * <p>
   * As far as customizing how the table looks in the printed result,
   * <code>JTable</code> itself will take care of hiding the selection
   * and focus during printing. For additional customizations, your
   * renderers or painting code can customize the look based on the value
   * of {@link javax.swing.JComponent#isPaintingForPrint()}
   * <p>
   * Also, <i>before</i> calling this method you may wish to <i>first</i>
   * modify the state of the table, such as to cancel cell editing or
   * have the user size the table appropriately. However, you must not
   * modify the state of the table <i>after</i> this <code>Printable</code>
   * has been fetched (invalid modifications include changes in size or
   * underlying data). The behavior of the returned <code>Printable</code>
   * is undefined once the table has been changed.
   *
   * @param printMode the printing mode that the printable should use
   * @param headerFormat a <code>MessageFormat</code> specifying the text to be used in printing a
   * header, or null for none
   * @param footerFormat a <code>MessageFormat</code> specifying the text to be used in printing a
   * footer, or null for none
   * @return a <code>Printable</code> for printing this JTable
   * @see #print(JTable.PrintMode, MessageFormat, MessageFormat, boolean, PrintRequestAttributeSet,
   * boolean)
   * @see Printable
   * @see PrinterJob
   * @since 1.5
   */
  public Printable getPrintable(PrintMode printMode,
      MessageFormat headerFormat,
      MessageFormat footerFormat) {

    return new TablePrintable(this, printMode, headerFormat, footerFormat);
  }


  /**
   * A <code>Printable</code> implementation that wraps another
   * <code>Printable</code>, making it safe for printing on another thread.
   */
  private class ThreadSafePrintable implements Printable {

    /**
     * The delegate <code>Printable</code>.
     */
    private Printable printDelegate;

    /**
     * To communicate any return value when delegating.
     */
    private int retVal;

    /**
     * To communicate any <code>Throwable</code> when delegating.
     */
    private Throwable retThrowable;

    /**
     * Construct a <code>ThreadSafePrintable</code> around the given
     * delegate.
     *
     * @param printDelegate the <code>Printable</code> to delegate to
     */
    public ThreadSafePrintable(Printable printDelegate) {
      this.printDelegate = printDelegate;
    }

    /**
     * Prints the specified page into the given {@link Graphics}
     * context, in the specified format.
     * <p>
     * Regardless of what thread this method is called on, all calls into
     * the delegate will be done on the event-dispatch thread.
     *
     * @param graphics the context into which the page is drawn
     * @param pageFormat the size and orientation of the page being drawn
     * @param pageIndex the zero based index of the page to be drawn
     * @return PAGE_EXISTS if the page is rendered successfully, or NO_SUCH_PAGE if a non-existent
     * page index is specified
     * @throws PrinterException if an error causes printing to be aborted
     */
    public int print(final Graphics graphics,
        final PageFormat pageFormat,
        final int pageIndex) throws PrinterException {

      // We'll use this Runnable
      Runnable runnable = new Runnable() {
        public synchronized void run() {
          try {
            // call into the delegate and save the return value
            retVal = printDelegate.print(graphics, pageFormat, pageIndex);
          } catch (Throwable throwable) {
            // save any Throwable to be rethrown
            retThrowable = throwable;
          } finally {
            // notify the caller that we're done
            notifyAll();
          }
        }
      };

      synchronized (runnable) {
        // make sure these are initialized
        retVal = -1;
        retThrowable = null;

        // call into the EDT
        SwingUtilities.invokeLater(runnable);

        // wait for the runnable to finish
        while (retVal == -1 && retThrowable == null) {
          try {
            runnable.wait();
          } catch (InterruptedException ie) {
            // short process, safe to ignore interrupts
          }
        }

        // if the delegate threw a throwable, rethrow it here
        if (retThrowable != null) {
          if (retThrowable instanceof PrinterException) {
            throw (PrinterException) retThrowable;
          } else if (retThrowable instanceof RuntimeException) {
            throw (RuntimeException) retThrowable;
          } else if (retThrowable instanceof Error) {
            throw (Error) retThrowable;
          }

          // can not happen
          throw new AssertionError(retThrowable);
        }

        return retVal;
      }
    }
  }

/////////////////
// Accessibility support
////////////////

  /**
   * Gets the AccessibleContext associated with this JTable.
   * For tables, the AccessibleContext takes the form of an
   * AccessibleJTable.
   * A new AccessibleJTable instance is created if necessary.
   *
   * @return an AccessibleJTable that serves as the AccessibleContext of this JTable
   */
  public AccessibleContext getAccessibleContext() {
    if (accessibleContext == null) {
      accessibleContext = new AccessibleJTable();
    }
    return accessibleContext;
  }

  //
  // *** should also implement AccessibleSelection?
  // *** and what's up with keyboard navigation/manipulation?
  //

  /**
   * This class implements accessibility support for the
   * <code>JTable</code> class.  It provides an implementation of the
   * Java Accessibility API appropriate to table user-interface elements.
   * <p>
   * <strong>Warning:</strong>
   * Serialized objects of this class will not be compatible with
   * future Swing releases. The current serialization support is
   * appropriate for short term storage or RMI between applications running
   * the same version of Swing.  As of 1.4, support for long term storage
   * of all JavaBeans&trade;
   * has been added to the <code>java.beans</code> package.
   * Please see {@link java.beans.XMLEncoder}.
   */
  protected class AccessibleJTable extends AccessibleJComponent
      implements AccessibleSelection, ListSelectionListener, TableModelListener,
      TableColumnModelListener, CellEditorListener, PropertyChangeListener,
      AccessibleExtendedTable {

    int previousFocusedRow;
    int previousFocusedCol;

    /**
     * AccessibleJTable constructor
     *
     * @since 1.5
     */
    protected AccessibleJTable() {
      super();
      JTable.this.addPropertyChangeListener(this);
      JTable.this.getSelectionModel().addListSelectionListener(this);
      TableColumnModel tcm = JTable.this.getColumnModel();
      tcm.addColumnModelListener(this);
      tcm.getSelectionModel().addListSelectionListener(this);
      JTable.this.getModel().addTableModelListener(this);
      previousFocusedRow = JTable.this.getSelectionModel().
          getLeadSelectionIndex();
      previousFocusedCol = JTable.this.getColumnModel().
          getSelectionModel().getLeadSelectionIndex();
    }

    // Listeners to track model, etc. changes to as to re-place the other
    // listeners

    /**
     * Track changes to selection model, column model, etc. so as to
     * be able to re-place listeners on those in order to pass on
     * information to the Accessibility PropertyChange mechanism
     */
    public void propertyChange(PropertyChangeEvent e) {
      String name = e.getPropertyName();
      Object oldValue = e.getOldValue();
      Object newValue = e.getNewValue();

      // re-set tableModel listeners
      if (name.compareTo("model") == 0) {

        if (oldValue != null && oldValue instanceof TableModel) {
          ((TableModel) oldValue).removeTableModelListener(this);
        }
        if (newValue != null && newValue instanceof TableModel) {
          ((TableModel) newValue).addTableModelListener(this);
        }

        // re-set selectionModel listeners
      } else if (name.compareTo("selectionModel") == 0) {

        Object source = e.getSource();
        if (source == JTable.this) {    // row selection model

          if (oldValue != null &&
              oldValue instanceof ListSelectionModel) {
            ((ListSelectionModel) oldValue).removeListSelectionListener(this);
          }
          if (newValue != null &&
              newValue instanceof ListSelectionModel) {
            ((ListSelectionModel) newValue).addListSelectionListener(this);
          }

        } else if (source == JTable.this.getColumnModel()) {

          if (oldValue != null &&
              oldValue instanceof ListSelectionModel) {
            ((ListSelectionModel) oldValue).removeListSelectionListener(this);
          }
          if (newValue != null &&
              newValue instanceof ListSelectionModel) {
            ((ListSelectionModel) newValue).addListSelectionListener(this);
          }

        } else {
          //        System.out.println("!!! Bug in source of selectionModel propertyChangeEvent");
        }

        // re-set columnModel listeners
        // and column's selection property listener as well
      } else if (name.compareTo("columnModel") == 0) {

        if (oldValue != null && oldValue instanceof TableColumnModel) {
          TableColumnModel tcm = (TableColumnModel) oldValue;
          tcm.removeColumnModelListener(this);
          tcm.getSelectionModel().removeListSelectionListener(this);
        }
        if (newValue != null && newValue instanceof TableColumnModel) {
          TableColumnModel tcm = (TableColumnModel) newValue;
          tcm.addColumnModelListener(this);
          tcm.getSelectionModel().addListSelectionListener(this);
        }

        // re-se cellEditor listeners
      } else if (name.compareTo("tableCellEditor") == 0) {

        if (oldValue != null && oldValue instanceof TableCellEditor) {
          ((TableCellEditor) oldValue).removeCellEditorListener(this);
        }
        if (newValue != null && newValue instanceof TableCellEditor) {
          ((TableCellEditor) newValue).addCellEditorListener(this);
        }
      }
    }

    // Listeners to echo changes to the AccessiblePropertyChange mechanism

    /*
         * Describes a change in the accessible table model.
         */
    protected class AccessibleJTableModelChange
        implements AccessibleTableModelChange {

      protected int type;
      protected int firstRow;
      protected int lastRow;
      protected int firstColumn;
      protected int lastColumn;

      protected AccessibleJTableModelChange(int type, int firstRow,
          int lastRow, int firstColumn,
          int lastColumn) {
        this.type = type;
        this.firstRow = firstRow;
        this.lastRow = lastRow;
        this.firstColumn = firstColumn;
        this.lastColumn = lastColumn;
      }

      public int getType() {
        return type;
      }

      public int getFirstRow() {
        return firstRow;
      }

      public int getLastRow() {
        return lastRow;
      }

      public int getFirstColumn() {
        return firstColumn;
      }

      public int getLastColumn() {
        return lastColumn;
      }
    }

    /**
     * Track changes to the table contents
     */
    public void tableChanged(TableModelEvent e) {
      firePropertyChange(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
          null, null);
      if (e != null) {
        int firstColumn = e.getColumn();
        int lastColumn = e.getColumn();
        if (firstColumn == TableModelEvent.ALL_COLUMNS) {
          firstColumn = 0;
          lastColumn = getColumnCount() - 1;
        }

        // Fire a property change event indicating the table model
        // has changed.
        AccessibleJTableModelChange change =
            new AccessibleJTableModelChange(e.getType(),
                e.getFirstRow(),
                e.getLastRow(),
                firstColumn,
                lastColumn);
        firePropertyChange(AccessibleContext.ACCESSIBLE_TABLE_MODEL_CHANGED,
            null, change);
      }
    }

    /**
     * Track changes to the table contents (row insertions)
     */
    public void tableRowsInserted(TableModelEvent e) {
      firePropertyChange(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
          null, null);

      // Fire a property change event indicating the table model
      // has changed.
      int firstColumn = e.getColumn();
      int lastColumn = e.getColumn();
      if (firstColumn == TableModelEvent.ALL_COLUMNS) {
        firstColumn = 0;
        lastColumn = getColumnCount() - 1;
      }
      AccessibleJTableModelChange change =
          new AccessibleJTableModelChange(e.getType(),
              e.getFirstRow(),
              e.getLastRow(),
              firstColumn,
              lastColumn);
      firePropertyChange(AccessibleContext.ACCESSIBLE_TABLE_MODEL_CHANGED,
          null, change);
    }

    /**
     * Track changes to the table contents (row deletions)
     */
    public void tableRowsDeleted(TableModelEvent e) {
      firePropertyChange(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
          null, null);

      // Fire a property change event indicating the table model
      // has changed.
      int firstColumn = e.getColumn();
      int lastColumn = e.getColumn();
      if (firstColumn == TableModelEvent.ALL_COLUMNS) {
        firstColumn = 0;
        lastColumn = getColumnCount() - 1;
      }
      AccessibleJTableModelChange change =
          new AccessibleJTableModelChange(e.getType(),
              e.getFirstRow(),
              e.getLastRow(),
              firstColumn,
              lastColumn);
      firePropertyChange(AccessibleContext.ACCESSIBLE_TABLE_MODEL_CHANGED,
          null, change);
    }

    /**
     * Track changes to the table contents (column insertions)
     */
    public void columnAdded(TableColumnModelEvent e) {
      firePropertyChange(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
          null, null);

      // Fire a property change event indicating the table model
      // has changed.
      int type = AccessibleTableModelChange.INSERT;
      AccessibleJTableModelChange change =
          new AccessibleJTableModelChange(type,
              0,
              0,
              e.getFromIndex(),
              e.getToIndex());
      firePropertyChange(AccessibleContext.ACCESSIBLE_TABLE_MODEL_CHANGED,
          null, change);
    }

    /**
     * Track changes to the table contents (column deletions)
     */
    public void columnRemoved(TableColumnModelEvent e) {
      firePropertyChange(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
          null, null);
      // Fire a property change event indicating the table model
      // has changed.
      int type = AccessibleTableModelChange.DELETE;
      AccessibleJTableModelChange change =
          new AccessibleJTableModelChange(type,
              0,
              0,
              e.getFromIndex(),
              e.getToIndex());
      firePropertyChange(AccessibleContext.ACCESSIBLE_TABLE_MODEL_CHANGED,
          null, change);
    }

    /**
     * Track changes of a column repositioning.
     *
     * @see TableColumnModelListener
     */
    public void columnMoved(TableColumnModelEvent e) {
      firePropertyChange(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
          null, null);

      // Fire property change events indicating the table model
      // has changed.
      int type = AccessibleTableModelChange.DELETE;
      AccessibleJTableModelChange change =
          new AccessibleJTableModelChange(type,
              0,
              0,
              e.getFromIndex(),
              e.getFromIndex());
      firePropertyChange(AccessibleContext.ACCESSIBLE_TABLE_MODEL_CHANGED,
          null, change);

      int type2 = AccessibleTableModelChange.INSERT;
      AccessibleJTableModelChange change2 =
          new AccessibleJTableModelChange(type2,
              0,
              0,
              e.getToIndex(),
              e.getToIndex());
      firePropertyChange(AccessibleContext.ACCESSIBLE_TABLE_MODEL_CHANGED,
          null, change2);
    }

    /**
     * Track changes of a column moving due to margin changes.
     *
     * @see TableColumnModelListener
     */
    public void columnMarginChanged(ChangeEvent e) {
      firePropertyChange(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
          null, null);
    }

    /**
     * Track that the selection model of the TableColumnModel changed.
     *
     * @see TableColumnModelListener
     */
    public void columnSelectionChanged(ListSelectionEvent e) {
      // we should now re-place our TableColumn listener
    }

    /**
     * Track changes to a cell's contents.
     *
     * Invoked when editing is finished. The changes are saved, the
     * editor object is discarded, and the cell is rendered once again.
     *
     * @see CellEditorListener
     */
    public void editingStopped(ChangeEvent e) {
      // it'd be great if we could figure out which cell, and pass that
      // somehow as a parameter
      firePropertyChange(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
          null, null);
    }

    /**
     * Invoked when editing is canceled. The editor object is discarded
     * and the cell is rendered once again.
     *
     * @see CellEditorListener
     */
    public void editingCanceled(ChangeEvent e) {
      // nothing to report, 'cause nothing changed
    }

    /**
     * Track changes to table cell selections
     */
    public void valueChanged(ListSelectionEvent e) {
      firePropertyChange(AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY,
          Boolean.valueOf(false), Boolean.valueOf(true));

      // Using lead selection index to cover both cases: node selected and node
      // is focused but not selected (Ctrl+up/down)
      int focusedRow = JTable.this.getSelectionModel().getLeadSelectionIndex();
      int focusedCol = JTable.this.getColumnModel().getSelectionModel().
          getLeadSelectionIndex();

      if (focusedRow != previousFocusedRow ||
          focusedCol != previousFocusedCol) {
        Accessible oldA = getAccessibleAt(previousFocusedRow, previousFocusedCol);
        Accessible newA = getAccessibleAt(focusedRow, focusedCol);
        firePropertyChange(AccessibleContext.ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY,
            oldA, newA);
        previousFocusedRow = focusedRow;
        previousFocusedCol = focusedCol;
      }
    }

    // AccessibleContext support

    /**
     * Get the AccessibleSelection associated with this object.  In the
     * implementation of the Java Accessibility API for this class,
     * return this object, which is responsible for implementing the
     * AccessibleSelection interface on behalf of itself.
     *
     * @return this object
     */
    public AccessibleSelection getAccessibleSelection() {
      return this;
    }

    /**
     * Gets the role of this object.
     *
     * @return an instance of AccessibleRole describing the role of the object
     * @see AccessibleRole
     */
    public AccessibleRole getAccessibleRole() {
      return AccessibleRole.TABLE;
    }

    /**
     * Returns the <code>Accessible</code> child, if one exists,
     * contained at the local coordinate <code>Point</code>.
     *
     * @param p the point defining the top-left corner of the <code>Accessible</code>, given in the
     * coordinate space of the object's parent
     * @return the <code>Accessible</code>, if it exists, at the specified location; else
     * <code>null</code>
     */
    public Accessible getAccessibleAt(Point p) {
      int column = columnAtPoint(p);
      int row = rowAtPoint(p);

      if ((column != -1) && (row != -1)) {
        TableColumn aColumn = getColumnModel().getColumn(column);
        TableCellRenderer renderer = aColumn.getCellRenderer();
        if (renderer == null) {
          Class<?> columnClass = getColumnClass(column);
          renderer = getDefaultRenderer(columnClass);
        }
        Component component = renderer.getTableCellRendererComponent(
            JTable.this, null, false, false,
            row, column);
        return new AccessibleJTableCell(JTable.this, row, column,
            getAccessibleIndexAt(row, column));
      }
      return null;
    }

    /**
     * Returns the number of accessible children in the object.  If all
     * of the children of this object implement <code>Accessible</code>,
     * then this method should return the number of children of this object.
     *
     * @return the number of accessible children in the object
     */
    public int getAccessibleChildrenCount() {
      return (JTable.this.getColumnCount() * JTable.this.getRowCount());
    }

    /**
     * Returns the nth <code>Accessible</code> child of the object.
     *
     * @param i zero-based index of child
     * @return the nth Accessible child of the object
     */
    public Accessible getAccessibleChild(int i) {
      if (i < 0 || i >= getAccessibleChildrenCount()) {
        return null;
      } else {
        // children increase across, and then down, for tables
        // (arbitrary decision)
        int column = getAccessibleColumnAtIndex(i);
        int row = getAccessibleRowAtIndex(i);

        TableColumn aColumn = getColumnModel().getColumn(column);
        TableCellRenderer renderer = aColumn.getCellRenderer();
        if (renderer == null) {
          Class<?> columnClass = getColumnClass(column);
          renderer = getDefaultRenderer(columnClass);
        }
        Component component = renderer.getTableCellRendererComponent(
            JTable.this, null, false, false,
            row, column);
        return new AccessibleJTableCell(JTable.this, row, column,
            getAccessibleIndexAt(row, column));
      }
    }

    // AccessibleSelection support

    /**
     * Returns the number of <code>Accessible</code> children
     * currently selected.
     * If no children are selected, the return value will be 0.
     *
     * @return the number of items currently selected
     */
    public int getAccessibleSelectionCount() {
      int rowsSel = JTable.this.getSelectedRowCount();
      int colsSel = JTable.this.getSelectedColumnCount();

      if (JTable.this.cellSelectionEnabled) { // a contiguous block
        return rowsSel * colsSel;

      } else {
        // a column swath and a row swath, with a shared block
        if (JTable.this.getRowSelectionAllowed() &&
            JTable.this.getColumnSelectionAllowed()) {
          return rowsSel * JTable.this.getColumnCount() +
              colsSel * JTable.this.getRowCount() -
              rowsSel * colsSel;

          // just one or more rows in selection
        } else if (JTable.this.getRowSelectionAllowed()) {
          return rowsSel * JTable.this.getColumnCount();

          // just one or more rows in selection
        } else if (JTable.this.getColumnSelectionAllowed()) {
          return colsSel * JTable.this.getRowCount();

        } else {
          return 0;    // JTable doesn't allow selections
        }
      }
    }

    /**
     * Returns an <code>Accessible</code> representing the
     * specified selected child in the object.  If there
     * isn't a selection, or there are fewer children selected
     * than the integer passed in, the return
     * value will be <code>null</code>.
     * <p>Note that the index represents the i-th selected child, which
     * is different from the i-th child.
     *
     * @param i the zero-based index of selected children
     * @return the i-th selected child
     * @see #getAccessibleSelectionCount
     */
    public Accessible getAccessibleSelection(int i) {
      if (i < 0 || i > getAccessibleSelectionCount()) {
        return null;
      }

      int rowsSel = JTable.this.getSelectedRowCount();
      int colsSel = JTable.this.getSelectedColumnCount();
      int rowIndicies[] = getSelectedRows();
      int colIndicies[] = getSelectedColumns();
      int ttlCols = JTable.this.getColumnCount();
      int ttlRows = JTable.this.getRowCount();
      int r;
      int c;

      if (JTable.this.cellSelectionEnabled) { // a contiguous block
        r = rowIndicies[i / colsSel];
        c = colIndicies[i % colsSel];
        return getAccessibleChild((r * ttlCols) + c);
      } else {

        // a column swath and a row swath, with a shared block
        if (JTable.this.getRowSelectionAllowed() &&
            JTable.this.getColumnSelectionAllowed()) {

          // Situation:
          //   We have a table, like the 6x3 table below,
          //   wherein three colums and one row selected
          //   (selected cells marked with "*", unselected "0"):
          //
          //            0 * 0 * * 0
          //            * * * * * *
          //            0 * 0 * * 0
          //

          // State machine below walks through the array of
          // selected rows in two states: in a selected row,
          // and not in one; continuing until we are in a row
          // in which the ith selection exists.  Then we return
          // the appropriate cell.  In the state machine, we
          // always do rows above the "current" selected row first,
          // then the cells in the selected row.  If we're done
          // with the state machine before finding the requested
          // selected child, we handle the rows below the last
          // selected row at the end.
          //
          int curIndex = i;
          final int IN_ROW = 0;
          final int NOT_IN_ROW = 1;
          int state = (rowIndicies[0] == 0 ? IN_ROW : NOT_IN_ROW);
          int j = 0;
          int prevRow = -1;
          while (j < rowIndicies.length) {
            switch (state) {

              case IN_ROW:   // on individual row full of selections
                if (curIndex < ttlCols) { // it's here!
                  c = curIndex % ttlCols;
                  r = rowIndicies[j];
                  return getAccessibleChild((r * ttlCols) + c);
                } else {                               // not here
                  curIndex -= ttlCols;
                }
                // is the next row in table selected or not?
                if (j + 1 == rowIndicies.length ||
                    rowIndicies[j] != rowIndicies[j + 1] - 1) {
                  state = NOT_IN_ROW;
                  prevRow = rowIndicies[j];
                }
                j++;  // we didn't return earlier, so go to next row
                break;

              case NOT_IN_ROW:  // sparse bunch of rows of selections
                if (curIndex <
                    (colsSel * (rowIndicies[j] -
                        (prevRow == -1 ? 0 : (prevRow + 1))))) {

                  // it's here!
                  c = colIndicies[curIndex % colsSel];
                  r = (j > 0 ? rowIndicies[j - 1] + 1 : 0)
                      + curIndex / colsSel;
                  return getAccessibleChild((r * ttlCols) + c);
                } else {                               // not here
                  curIndex -= colsSel * (rowIndicies[j] -
                      (prevRow == -1 ? 0 : (prevRow + 1)));
                }
                state = IN_ROW;
                break;
            }
          }
          // we got here, so we didn't find it yet; find it in
          // the last sparse bunch of rows
          if (curIndex <
              (colsSel * (ttlRows -
                  (prevRow == -1 ? 0 : (prevRow + 1))))) { // it's here!
            c = colIndicies[curIndex % colsSel];
            r = rowIndicies[j - 1] + curIndex / colsSel + 1;
            return getAccessibleChild((r * ttlCols) + c);
          } else {                               // not here
            // we shouldn't get to this spot in the code!
//                      System.out.println("Bug in AccessibleJTable.getAccessibleSelection()");
          }

          // one or more rows selected
        } else if (JTable.this.getRowSelectionAllowed()) {
          c = i % ttlCols;
          r = rowIndicies[i / ttlCols];
          return getAccessibleChild((r * ttlCols) + c);

          // one or more columns selected
        } else if (JTable.this.getColumnSelectionAllowed()) {
          c = colIndicies[i % colsSel];
          r = i / colsSel;
          return getAccessibleChild((r * ttlCols) + c);
        }
      }
      return null;
    }

    /**
     * Determines if the current child of this object is selected.
     *
     * @param i the zero-based index of the child in this <code>Accessible</code> object
     * @return true if the current child of this object is selected
     * @see AccessibleContext#getAccessibleChild
     */
    public boolean isAccessibleChildSelected(int i) {
      int column = getAccessibleColumnAtIndex(i);
      int row = getAccessibleRowAtIndex(i);
      return JTable.this.isCellSelected(row, column);
    }

    /**
     * Adds the specified <code>Accessible</code> child of the
     * object to the object's selection.  If the object supports
     * multiple selections, the specified child is added to
     * any existing selection, otherwise
     * it replaces any existing selection in the object.  If the
     * specified child is already selected, this method has no effect.
     * <p>
     * This method only works on <code>JTable</code>s which have
     * individual cell selection enabled.
     *
     * @param i the zero-based index of the child
     * @see AccessibleContext#getAccessibleChild
     */
    public void addAccessibleSelection(int i) {
      // TIGER - 4495286
      int column = getAccessibleColumnAtIndex(i);
      int row = getAccessibleRowAtIndex(i);
      JTable.this.changeSelection(row, column, true, false);
    }

    /**
     * Removes the specified child of the object from the object's
     * selection.  If the specified item isn't currently selected, this
     * method has no effect.
     * <p>
     * This method only works on <code>JTables</code> which have
     * individual cell selection enabled.
     *
     * @param i the zero-based index of the child
     * @see AccessibleContext#getAccessibleChild
     */
    public void removeAccessibleSelection(int i) {
      if (JTable.this.cellSelectionEnabled) {
        int column = getAccessibleColumnAtIndex(i);
        int row = getAccessibleRowAtIndex(i);
        JTable.this.removeRowSelectionInterval(row, row);
        JTable.this.removeColumnSelectionInterval(column, column);
      }
    }

    /**
     * Clears the selection in the object, so that no children in the
     * object are selected.
     */
    public void clearAccessibleSelection() {
      JTable.this.clearSelection();
    }

    /**
     * Causes every child of the object to be selected, but only
     * if the <code>JTable</code> supports multiple selections,
     * and if individual cell selection is enabled.
     */
    public void selectAllAccessibleSelection() {
      if (JTable.this.cellSelectionEnabled) {
        JTable.this.selectAll();
      }
    }

    // begin AccessibleExtendedTable implementation -------------

    /**
     * Returns the row number of an index in the table.
     *
     * @param index the zero-based index in the table
     * @return the zero-based row of the table if one exists; otherwise -1.
     * @since 1.4
     */
    public int getAccessibleRow(int index) {
      return getAccessibleRowAtIndex(index);
    }

    /**
     * Returns the column number of an index in the table.
     *
     * @param index the zero-based index in the table
     * @return the zero-based column of the table if one exists; otherwise -1.
     * @since 1.4
     */
    public int getAccessibleColumn(int index) {
      return getAccessibleColumnAtIndex(index);
    }

    /**
     * Returns the index at a row and column in the table.
     *
     * @param r zero-based row of the table
     * @param c zero-based column of the table
     * @return the zero-based index in the table if one exists; otherwise -1.
     * @since 1.4
     */
    public int getAccessibleIndex(int r, int c) {
      return getAccessibleIndexAt(r, c);
    }

    // end of AccessibleExtendedTable implementation ------------

    // start of AccessibleTable implementation ------------------

    private Accessible caption;
    private Accessible summary;
    private Accessible[] rowDescription;
    private Accessible[] columnDescription;

    /**
     * Gets the <code>AccessibleTable</code> associated with this
     * object.  In the implementation of the Java Accessibility
     * API for this class, return this object, which is responsible
     * for implementing the <code>AccessibleTables</code> interface
     * on behalf of itself.
     *
     * @return this object
     * @since 1.3
     */
    public AccessibleTable getAccessibleTable() {
      return this;
    }

    /**
     * Returns the caption for the table.
     *
     * @return the caption for the table
     * @since 1.3
     */
    public Accessible getAccessibleCaption() {
      return this.caption;
    }

    /**
     * Sets the caption for the table.
     *
     * @param a the caption for the table
     * @since 1.3
     */
    public void setAccessibleCaption(Accessible a) {
      Accessible oldCaption = caption;
      this.caption = a;
      firePropertyChange(AccessibleContext.ACCESSIBLE_TABLE_CAPTION_CHANGED,
          oldCaption, this.caption);
    }

    /**
     * Returns the summary description of the table.
     *
     * @return the summary description of the table
     * @since 1.3
     */
    public Accessible getAccessibleSummary() {
      return this.summary;
    }

    /**
     * Sets the summary description of the table.
     *
     * @param a the summary description of the table
     * @since 1.3
     */
    public void setAccessibleSummary(Accessible a) {
      Accessible oldSummary = summary;
      this.summary = a;
      firePropertyChange(AccessibleContext.ACCESSIBLE_TABLE_SUMMARY_CHANGED,
          oldSummary, this.summary);
    }

    /*
         * Returns the total number of rows in this table.
         *
         * @return the total number of rows in this table
         */
    public int getAccessibleRowCount() {
      return JTable.this.getRowCount();
    }

    /*
         * Returns the total number of columns in the table.
         *
         * @return the total number of columns in the table
         */
    public int getAccessibleColumnCount() {
      return JTable.this.getColumnCount();
    }

    /*
         * Returns the <code>Accessible</code> at a specified row
         * and column in the table.
         *
         * @param r zero-based row of the table
         * @param c zero-based column of the table
         * @return the <code>Accessible</code> at the specified row and column
         * in the table
         */
    public Accessible getAccessibleAt(int r, int c) {
      return getAccessibleChild((r * getAccessibleColumnCount()) + c);
    }

    /**
     * Returns the number of rows occupied by the <code>Accessible</code>
     * at a specified row and column in the table.
     *
     * @return the number of rows occupied by the <code>Accessible</code> at a specified row and
     * column in the table
     * @since 1.3
     */
    public int getAccessibleRowExtentAt(int r, int c) {
      return 1;
    }

    /**
     * Returns the number of columns occupied by the
     * <code>Accessible</code> at a given (row, column).
     *
     * @return the number of columns occupied by the <code>Accessible</code> at a specified row and
     * column in the table
     * @since 1.3
     */
    public int getAccessibleColumnExtentAt(int r, int c) {
      return 1;
    }

    /**
     * Returns the row headers as an <code>AccessibleTable</code>.
     *
     * @return an <code>AccessibleTable</code> representing the row headers
     * @since 1.3
     */
    public AccessibleTable getAccessibleRowHeader() {
      // row headers are not supported
      return null;
    }

    /**
     * Sets the row headers as an <code>AccessibleTable</code>.
     *
     * @param a an <code>AccessibleTable</code> representing the row headers
     * @since 1.3
     */
    public void setAccessibleRowHeader(AccessibleTable a) {
      // row headers are not supported
    }

    /**
     * Returns the column headers as an <code>AccessibleTable</code>.
     *
     * @return an <code>AccessibleTable</code> representing the column headers, or <code>null</code>
     * if the table header is <code>null</code>
     * @since 1.3
     */
    public AccessibleTable getAccessibleColumnHeader() {
      JTableHeader header = JTable.this.getTableHeader();
      return header == null ? null : new AccessibleTableHeader(header);
    }

    /*
         * Private class representing a table column header
         */
    private class AccessibleTableHeader implements AccessibleTable {

      private JTableHeader header;
      private TableColumnModel headerModel;

      AccessibleTableHeader(JTableHeader header) {
        this.header = header;
        this.headerModel = header.getColumnModel();
      }

      /**
       * Returns the caption for the table.
       *
       * @return the caption for the table
       */
      public Accessible getAccessibleCaption() {
        return null;
      }


      /**
       * Sets the caption for the table.
       *
       * @param a the caption for the table
       */
      public void setAccessibleCaption(Accessible a) {
      }

      /**
       * Returns the summary description of the table.
       *
       * @return the summary description of the table
       */
      public Accessible getAccessibleSummary() {
        return null;
      }

      /**
       * Sets the summary description of the table
       *
       * @param a the summary description of the table
       */
      public void setAccessibleSummary(Accessible a) {
      }

      /**
       * Returns the number of rows in the table.
       *
       * @return the number of rows in the table
       */
      public int getAccessibleRowCount() {
        return 1;
      }

      /**
       * Returns the number of columns in the table.
       *
       * @return the number of columns in the table
       */
      public int getAccessibleColumnCount() {
        return headerModel.getColumnCount();
      }

      /**
       * Returns the Accessible at a specified row and column
       * in the table.
       *
       * @param row zero-based row of the table
       * @param column zero-based column of the table
       * @return the Accessible at the specified row and column
       */
      public Accessible getAccessibleAt(int row, int column) {

        // TIGER - 4715503
        TableColumn aColumn = headerModel.getColumn(column);
        TableCellRenderer renderer = aColumn.getHeaderRenderer();
        if (renderer == null) {
          renderer = header.getDefaultRenderer();
        }
        Component component = renderer.getTableCellRendererComponent(
            header.getTable(),
            aColumn.getHeaderValue(), false, false,
            -1, column);

        return new AccessibleJTableHeaderCell(row, column,
            JTable.this.getTableHeader(),
            component);
      }

      /**
       * Returns the number of rows occupied by the Accessible at
       * a specified row and column in the table.
       *
       * @return the number of rows occupied by the Accessible at a given specified (row, column)
       */
      public int getAccessibleRowExtentAt(int r, int c) {
        return 1;
      }

      /**
       * Returns the number of columns occupied by the Accessible at
       * a specified row and column in the table.
       *
       * @return the number of columns occupied by the Accessible at a given specified row and
       * column
       */
      public int getAccessibleColumnExtentAt(int r, int c) {
        return 1;
      }

      /**
       * Returns the row headers as an AccessibleTable.
       *
       * @return an AccessibleTable representing the row headers
       */
      public AccessibleTable getAccessibleRowHeader() {
        return null;
      }

      /**
       * Sets the row headers.
       *
       * @param table an AccessibleTable representing the row headers
       */
      public void setAccessibleRowHeader(AccessibleTable table) {
      }

      /**
       * Returns the column headers as an AccessibleTable.
       *
       * @return an AccessibleTable representing the column headers
       */
      public AccessibleTable getAccessibleColumnHeader() {
        return null;
      }

      /**
       * Sets the column headers.
       *
       * @param table an AccessibleTable representing the column headers
       * @since 1.3
       */
      public void setAccessibleColumnHeader(AccessibleTable table) {
      }

      /**
       * Returns the description of the specified row in the table.
       *
       * @param r zero-based row of the table
       * @return the description of the row
       * @since 1.3
       */
      public Accessible getAccessibleRowDescription(int r) {
        return null;
      }

      /**
       * Sets the description text of the specified row of the table.
       *
       * @param r zero-based row of the table
       * @param a the description of the row
       * @since 1.3
       */
      public void setAccessibleRowDescription(int r, Accessible a) {
      }

      /**
       * Returns the description text of the specified column in the table.
       *
       * @param c zero-based column of the table
       * @return the text description of the column
       * @since 1.3
       */
      public Accessible getAccessibleColumnDescription(int c) {
        return null;
      }

      /**
       * Sets the description text of the specified column in the table.
       *
       * @param c zero-based column of the table
       * @param a the text description of the column
       * @since 1.3
       */
      public void setAccessibleColumnDescription(int c, Accessible a) {
      }

      /**
       * Returns a boolean value indicating whether the accessible at
       * a specified row and column is selected.
       *
       * @param r zero-based row of the table
       * @param c zero-based column of the table
       * @return the boolean value true if the accessible at the row and column is selected.
       * Otherwise, the boolean value false
       * @since 1.3
       */
      public boolean isAccessibleSelected(int r, int c) {
        return false;
      }

      /**
       * Returns a boolean value indicating whether the specified row
       * is selected.
       *
       * @param r zero-based row of the table
       * @return the boolean value true if the specified row is selected. Otherwise, false.
       * @since 1.3
       */
      public boolean isAccessibleRowSelected(int r) {
        return false;
      }

      /**
       * Returns a boolean value indicating whether the specified column
       * is selected.
       *
       * @param c zero-based column of the table
       * @return the boolean value true if the specified column is selected. Otherwise, false.
       * @since 1.3
       */
      public boolean isAccessibleColumnSelected(int c) {
        return false;
      }

      /**
       * Returns the selected rows in a table.
       *
       * @return an array of selected rows where each element is a zero-based row of the table
       * @since 1.3
       */
      public int[] getSelectedAccessibleRows() {
        return new int[0];
      }

      /**
       * Returns the selected columns in a table.
       *
       * @return an array of selected columns where each element is a zero-based column of the table
       * @since 1.3
       */
      public int[] getSelectedAccessibleColumns() {
        return new int[0];
      }
    }


    /**
     * Sets the column headers as an <code>AccessibleTable</code>.
     *
     * @param a an <code>AccessibleTable</code> representing the column headers
     * @since 1.3
     */
    public void setAccessibleColumnHeader(AccessibleTable a) {
      // XXX not implemented
    }

    /**
     * Returns the description of the specified row in the table.
     *
     * @param r zero-based row of the table
     * @return the description of the row
     * @since 1.3
     */
    public Accessible getAccessibleRowDescription(int r) {
      if (r < 0 || r >= getAccessibleRowCount()) {
        throw new IllegalArgumentException(Integer.toString(r));
      }
      if (rowDescription == null) {
        return null;
      } else {
        return rowDescription[r];
      }
    }

    /**
     * Sets the description text of the specified row of the table.
     *
     * @param r zero-based row of the table
     * @param a the description of the row
     * @since 1.3
     */
    public void setAccessibleRowDescription(int r, Accessible a) {
      if (r < 0 || r >= getAccessibleRowCount()) {
        throw new IllegalArgumentException(Integer.toString(r));
      }
      if (rowDescription == null) {
        int numRows = getAccessibleRowCount();
        rowDescription = new Accessible[numRows];
      }
      rowDescription[r] = a;
    }

    /**
     * Returns the description of the specified column in the table.
     *
     * @param c zero-based column of the table
     * @return the description of the column
     * @since 1.3
     */
    public Accessible getAccessibleColumnDescription(int c) {
      if (c < 0 || c >= getAccessibleColumnCount()) {
        throw new IllegalArgumentException(Integer.toString(c));
      }
      if (columnDescription == null) {
        return null;
      } else {
        return columnDescription[c];
      }
    }

    /**
     * Sets the description text of the specified column of the table.
     *
     * @param c zero-based column of the table
     * @param a the description of the column
     * @since 1.3
     */
    public void setAccessibleColumnDescription(int c, Accessible a) {
      if (c < 0 || c >= getAccessibleColumnCount()) {
        throw new IllegalArgumentException(Integer.toString(c));
      }
      if (columnDescription == null) {
        int numColumns = getAccessibleColumnCount();
        columnDescription = new Accessible[numColumns];
      }
      columnDescription[c] = a;
    }

    /**
     * Returns a boolean value indicating whether the accessible at a
     * given (row, column) is selected.
     *
     * @param r zero-based row of the table
     * @param c zero-based column of the table
     * @return the boolean value true if the accessible at (row, column) is selected; otherwise, the
     * boolean value false
     * @since 1.3
     */
    public boolean isAccessibleSelected(int r, int c) {
      return JTable.this.isCellSelected(r, c);
    }

    /**
     * Returns a boolean value indicating whether the specified row
     * is selected.
     *
     * @param r zero-based row of the table
     * @return the boolean value true if the specified row is selected; otherwise, false
     * @since 1.3
     */
    public boolean isAccessibleRowSelected(int r) {
      return JTable.this.isRowSelected(r);
    }

    /**
     * Returns a boolean value indicating whether the specified column
     * is selected.
     *
     * @param c zero-based column of the table
     * @return the boolean value true if the specified column is selected; otherwise, false
     * @since 1.3
     */
    public boolean isAccessibleColumnSelected(int c) {
      return JTable.this.isColumnSelected(c);
    }

    /**
     * Returns the selected rows in a table.
     *
     * @return an array of selected rows where each element is a zero-based row of the table
     * @since 1.3
     */
    public int[] getSelectedAccessibleRows() {
      return JTable.this.getSelectedRows();
    }

    /**
     * Returns the selected columns in a table.
     *
     * @return an array of selected columns where each element is a zero-based column of the table
     * @since 1.3
     */
    public int[] getSelectedAccessibleColumns() {
      return JTable.this.getSelectedColumns();
    }

    /**
     * Returns the row at a given index into the table.
     *
     * @param i zero-based index into the table
     * @return the row at a given index
     * @since 1.3
     */
    public int getAccessibleRowAtIndex(int i) {
      int columnCount = getAccessibleColumnCount();
      if (columnCount == 0) {
        return -1;
      } else {
        return (i / columnCount);
      }
    }

    /**
     * Returns the column at a given index into the table.
     *
     * @param i zero-based index into the table
     * @return the column at a given index
     * @since 1.3
     */
    public int getAccessibleColumnAtIndex(int i) {
      int columnCount = getAccessibleColumnCount();
      if (columnCount == 0) {
        return -1;
      } else {
        return (i % columnCount);
      }
    }

    /**
     * Returns the index at a given (row, column) in the table.
     *
     * @param r zero-based row of the table
     * @param c zero-based column of the table
     * @return the index into the table
     * @since 1.3
     */
    public int getAccessibleIndexAt(int r, int c) {
      return ((r * getAccessibleColumnCount()) + c);
    }

    // end of AccessibleTable implementation --------------------

    /**
     * The class provides an implementation of the Java Accessibility
     * API appropriate to table cells.
     */
    protected class AccessibleJTableCell extends AccessibleContext
        implements Accessible, AccessibleComponent {

      private JTable parent;
      private int row;
      private int column;
      private int index;

      /**
       * Constructs an <code>AccessibleJTableHeaderEntry</code>.
       *
       * @since 1.4
       */
      public AccessibleJTableCell(JTable t, int r, int c, int i) {
        parent = t;
        row = r;
        column = c;
        index = i;
        this.setAccessibleParent(parent);
      }

      /**
       * Gets the <code>AccessibleContext</code> associated with this
       * component. In the implementation of the Java Accessibility
       * API for this class, return this object, which is its own
       * <code>AccessibleContext</code>.
       *
       * @return this object
       */
      public AccessibleContext getAccessibleContext() {
        return this;
      }

      /**
       * Gets the AccessibleContext for the table cell renderer.
       *
       * @return the <code>AccessibleContext</code> for the table cell renderer if one exists;
       * otherwise, returns <code>null</code>.
       * @since 1.6
       */
      protected AccessibleContext getCurrentAccessibleContext() {
        TableColumn aColumn = getColumnModel().getColumn(column);
        TableCellRenderer renderer = aColumn.getCellRenderer();
        if (renderer == null) {
          Class<?> columnClass = getColumnClass(column);
          renderer = getDefaultRenderer(columnClass);
        }
        Component component = renderer.getTableCellRendererComponent(
            JTable.this, getValueAt(row, column),
            false, false, row, column);
        if (component instanceof Accessible) {
          return component.getAccessibleContext();
        } else {
          return null;
        }
      }

      /**
       * Gets the table cell renderer component.
       *
       * @return the table cell renderer component if one exists; otherwise, returns
       * <code>null</code>.
       * @since 1.6
       */
      protected Component getCurrentComponent() {
        TableColumn aColumn = getColumnModel().getColumn(column);
        TableCellRenderer renderer = aColumn.getCellRenderer();
        if (renderer == null) {
          Class<?> columnClass = getColumnClass(column);
          renderer = getDefaultRenderer(columnClass);
        }
        return renderer.getTableCellRendererComponent(
            JTable.this, null, false, false,
            row, column);
      }

      // AccessibleContext methods

      /**
       * Gets the accessible name of this object.
       *
       * @return the localized name of the object; <code>null</code> if this object does not have a
       * name
       */
      public String getAccessibleName() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          String name = ac.getAccessibleName();
          if ((name != null) && (name != "")) {
            // return the cell renderer's AccessibleName
            return name;
          }
        }
        if ((accessibleName != null) && (accessibleName != "")) {
          return accessibleName;
        } else {
          // fall back to the client property
          return (String) getClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY);
        }
      }

      /**
       * Sets the localized accessible name of this object.
       *
       * @param s the new localized name of the object
       */
      public void setAccessibleName(String s) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          ac.setAccessibleName(s);
        } else {
          super.setAccessibleName(s);
        }
      }

      //
      // *** should check toolTip text for desc. (needs MouseEvent)
      //

      /**
       * Gets the accessible description of this object.
       *
       * @return the localized description of the object; <code>null</code> if this object does not
       * have a description
       */
      public String getAccessibleDescription() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          return ac.getAccessibleDescription();
        } else {
          return super.getAccessibleDescription();
        }
      }

      /**
       * Sets the accessible description of this object.
       *
       * @param s the new localized description of the object
       */
      public void setAccessibleDescription(String s) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          ac.setAccessibleDescription(s);
        } else {
          super.setAccessibleDescription(s);
        }
      }

      /**
       * Gets the role of this object.
       *
       * @return an instance of <code>AccessibleRole</code> describing the role of the object
       * @see AccessibleRole
       */
      public AccessibleRole getAccessibleRole() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          return ac.getAccessibleRole();
        } else {
          return AccessibleRole.UNKNOWN;
        }
      }

      /**
       * Gets the state set of this object.
       *
       * @return an instance of <code>AccessibleStateSet</code> containing the current state set of
       * the object
       * @see AccessibleState
       */
      public AccessibleStateSet getAccessibleStateSet() {
        AccessibleContext ac = getCurrentAccessibleContext();
        AccessibleStateSet as = null;

        if (ac != null) {
          as = ac.getAccessibleStateSet();
        }
        if (as == null) {
          as = new AccessibleStateSet();
        }
        Rectangle rjt = JTable.this.getVisibleRect();
        Rectangle rcell = JTable.this.getCellRect(row, column, false);
        if (rjt.intersects(rcell)) {
          as.add(AccessibleState.SHOWING);
        } else {
          if (as.contains(AccessibleState.SHOWING)) {
            as.remove(AccessibleState.SHOWING);
          }
        }
        if (parent.isCellSelected(row, column)) {
          as.add(AccessibleState.SELECTED);
        } else if (as.contains(AccessibleState.SELECTED)) {
          as.remove(AccessibleState.SELECTED);
        }
        if ((row == getSelectedRow()) && (column == getSelectedColumn())) {
          as.add(AccessibleState.ACTIVE);
        }
        as.add(AccessibleState.TRANSIENT);
        return as;
      }

      /**
       * Gets the <code>Accessible</code> parent of this object.
       *
       * @return the Accessible parent of this object; <code>null</code> if this object does not
       * have an <code>Accessible</code> parent
       */
      public Accessible getAccessibleParent() {
        return parent;
      }

      /**
       * Gets the index of this object in its accessible parent.
       *
       * @return the index of this object in its parent; -1 if this object does not have an
       * accessible parent
       * @see #getAccessibleParent
       */
      public int getAccessibleIndexInParent() {
        return index;
      }

      /**
       * Returns the number of accessible children in the object.
       *
       * @return the number of accessible children in the object
       */
      public int getAccessibleChildrenCount() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          return ac.getAccessibleChildrenCount();
        } else {
          return 0;
        }
      }

      /**
       * Returns the specified <code>Accessible</code> child of the
       * object.
       *
       * @param i zero-based index of child
       * @return the <code>Accessible</code> child of the object
       */
      public Accessible getAccessibleChild(int i) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          Accessible accessibleChild = ac.getAccessibleChild(i);
          ac.setAccessibleParent(this);
          return accessibleChild;
        } else {
          return null;
        }
      }

      /**
       * Gets the locale of the component. If the component
       * does not have a locale, then the locale of its parent
       * is returned.
       *
       * @return this component's locale; if this component does not have a locale, the locale of
       * its parent is returned
       * @throws IllegalComponentStateException if the <code>Component</code> does not have its own
       * locale and has not yet been added to a containment hierarchy such that the locale can be
       * determined from the containing parent
       * @see #setLocale
       */
      public Locale getLocale() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          return ac.getLocale();
        } else {
          return null;
        }
      }

      /**
       * Adds a <code>PropertyChangeListener</code> to the listener list.
       * The listener is registered for all properties.
       *
       * @param l the <code>PropertyChangeListener</code> to be added
       */
      public void addPropertyChangeListener(PropertyChangeListener l) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          ac.addPropertyChangeListener(l);
        } else {
          super.addPropertyChangeListener(l);
        }
      }

      /**
       * Removes a <code>PropertyChangeListener</code> from the
       * listener list. This removes a <code>PropertyChangeListener</code>
       * that was registered for all properties.
       *
       * @param l the <code>PropertyChangeListener</code> to be removed
       */
      public void removePropertyChangeListener(PropertyChangeListener l) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          ac.removePropertyChangeListener(l);
        } else {
          super.removePropertyChangeListener(l);
        }
      }

      /**
       * Gets the <code>AccessibleAction</code> associated with this
       * object if one exists.  Otherwise returns <code>null</code>.
       *
       * @return the <code>AccessibleAction</code>, or <code>null</code>
       */
      public AccessibleAction getAccessibleAction() {
        return getCurrentAccessibleContext().getAccessibleAction();
      }

      /**
       * Gets the <code>AccessibleComponent</code> associated with
       * this object if one exists.  Otherwise returns <code>null</code>.
       *
       * @return the <code>AccessibleComponent</code>, or <code>null</code>
       */
      public AccessibleComponent getAccessibleComponent() {
        return this; // to override getBounds()
      }

      /**
       * Gets the <code>AccessibleSelection</code> associated with
       * this object if one exists.  Otherwise returns <code>null</code>.
       *
       * @return the <code>AccessibleSelection</code>, or <code>null</code>
       */
      public AccessibleSelection getAccessibleSelection() {
        return getCurrentAccessibleContext().getAccessibleSelection();
      }

      /**
       * Gets the <code>AccessibleText</code> associated with this
       * object if one exists.  Otherwise returns <code>null</code>.
       *
       * @return the <code>AccessibleText</code>, or <code>null</code>
       */
      public AccessibleText getAccessibleText() {
        return getCurrentAccessibleContext().getAccessibleText();
      }

      /**
       * Gets the <code>AccessibleValue</code> associated with
       * this object if one exists.  Otherwise returns <code>null</code>.
       *
       * @return the <code>AccessibleValue</code>, or <code>null</code>
       */
      public AccessibleValue getAccessibleValue() {
        return getCurrentAccessibleContext().getAccessibleValue();
      }

      // AccessibleComponent methods

      /**
       * Gets the background color of this object.
       *
       * @return the background color, if supported, of the object; otherwise, <code>null</code>
       */
      public Color getBackground() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).getBackground();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.getBackground();
          } else {
            return null;
          }
        }
      }

      /**
       * Sets the background color of this object.
       *
       * @param c the new <code>Color</code> for the background
       */
      public void setBackground(Color c) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setBackground(c);
        } else {
          Component cp = getCurrentComponent();
          if (cp != null) {
            cp.setBackground(c);
          }
        }
      }

      /**
       * Gets the foreground color of this object.
       *
       * @return the foreground color, if supported, of the object; otherwise, <code>null</code>
       */
      public Color getForeground() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).getForeground();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.getForeground();
          } else {
            return null;
          }
        }
      }

      /**
       * Sets the foreground color of this object.
       *
       * @param c the new <code>Color</code> for the foreground
       */
      public void setForeground(Color c) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setForeground(c);
        } else {
          Component cp = getCurrentComponent();
          if (cp != null) {
            cp.setForeground(c);
          }
        }
      }

      /**
       * Gets the <code>Cursor</code> of this object.
       *
       * @return the <code>Cursor</code>, if supported, of the object; otherwise, <code>null</code>
       */
      public Cursor getCursor() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).getCursor();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.getCursor();
          } else {
            Accessible ap = getAccessibleParent();
            if (ap instanceof AccessibleComponent) {
              return ((AccessibleComponent) ap).getCursor();
            } else {
              return null;
            }
          }
        }
      }

      /**
       * Sets the <code>Cursor</code> of this object.
       *
       * @param c the new <code>Cursor</code> for the object
       */
      public void setCursor(Cursor c) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setCursor(c);
        } else {
          Component cp = getCurrentComponent();
          if (cp != null) {
            cp.setCursor(c);
          }
        }
      }

      /**
       * Gets the <code>Font</code> of this object.
       *
       * @return the <code>Font</code>,if supported, for the object; otherwise, <code>null</code>
       */
      public Font getFont() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).getFont();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.getFont();
          } else {
            return null;
          }
        }
      }

      /**
       * Sets the <code>Font</code> of this object.
       *
       * @param f the new <code>Font</code> for the object
       */
      public void setFont(Font f) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setFont(f);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.setFont(f);
          }
        }
      }

      /**
       * Gets the <code>FontMetrics</code> of this object.
       *
       * @param f the <code>Font</code>
       * @return the <code>FontMetrics</code> object, if supported; otherwise <code>null</code>
       * @see #getFont
       */
      public FontMetrics getFontMetrics(Font f) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).getFontMetrics(f);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.getFontMetrics(f);
          } else {
            return null;
          }
        }
      }

      /**
       * Determines if the object is enabled.
       *
       * @return true if object is enabled; otherwise, false
       */
      public boolean isEnabled() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).isEnabled();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.isEnabled();
          } else {
            return false;
          }
        }
      }

      /**
       * Sets the enabled state of the object.
       *
       * @param b if true, enables this object; otherwise, disables it
       */
      public void setEnabled(boolean b) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setEnabled(b);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.setEnabled(b);
          }
        }
      }

      /**
       * Determines if this object is visible.  Note: this means that the
       * object intends to be visible; however, it may not in fact be
       * showing on the screen because one of the objects that this object
       * is contained by is not visible.  To determine if an object is
       * showing on the screen, use <code>isShowing</code>.
       *
       * @return true if object is visible; otherwise, false
       */
      public boolean isVisible() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).isVisible();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.isVisible();
          } else {
            return false;
          }
        }
      }

      /**
       * Sets the visible state of the object.
       *
       * @param b if true, shows this object; otherwise, hides it
       */
      public void setVisible(boolean b) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setVisible(b);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.setVisible(b);
          }
        }
      }

      /**
       * Determines if the object is showing.  This is determined
       * by checking the visibility of the object and ancestors
       * of the object.  Note: this will return true even if the
       * object is obscured by another (for example,
       * it happens to be underneath a menu that was pulled down).
       *
       * @return true if the object is showing; otherwise, false
       */
      public boolean isShowing() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          if (ac.getAccessibleParent() != null) {
            return ((AccessibleComponent) ac).isShowing();
          } else {
            // Fixes 4529616 - AccessibleJTableCell.isShowing()
            // returns false when the cell on the screen
            // if no parent
            return isVisible();
          }
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.isShowing();
          } else {
            return false;
          }
        }
      }

      /**
       * Checks whether the specified point is within this
       * object's bounds, where the point's x and y coordinates
       * are defined to be relative to the coordinate system of
       * the object.
       *
       * @param p the <code>Point</code> relative to the coordinate system of the object
       * @return true if object contains <code>Point</code>; otherwise false
       */
      public boolean contains(Point p) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          Rectangle r = ((AccessibleComponent) ac).getBounds();
          return r.contains(p);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            Rectangle r = c.getBounds();
            return r.contains(p);
          } else {
            return getBounds().contains(p);
          }
        }
      }

      /**
       * Returns the location of the object on the screen.
       *
       * @return location of object on screen -- can be <code>null</code> if this object is not on
       * the screen
       */
      public Point getLocationOnScreen() {
        if (parent != null && parent.isShowing()) {
          Point parentLocation = parent.getLocationOnScreen();
          Point componentLocation = getLocation();
          componentLocation.translate(parentLocation.x, parentLocation.y);
          return componentLocation;
        } else {
          return null;
        }
      }

      /**
       * Gets the location of the object relative to the parent
       * in the form of a point specifying the object's
       * top-left corner in the screen's coordinate space.
       *
       * @return an instance of <code>Point</code> representing the top-left corner of the object's
       * bounds in the coordinate space of the screen; <code>null</code> if this object or its
       * parent are not on the screen
       */
      public Point getLocation() {
        if (parent != null) {
          Rectangle r = parent.getCellRect(row, column, false);
          if (r != null) {
            return r.getLocation();
          }
        }
        return null;
      }

      /**
       * Sets the location of the object relative to the parent.
       */
      public void setLocation(Point p) {
//              if ((parent != null)  && (parent.contains(p))) {
//                  ensureIndexIsVisible(indexInParent);
//              }
      }

      public Rectangle getBounds() {
        if (parent != null) {
          return parent.getCellRect(row, column, false);
        } else {
          return null;
        }
      }

      public void setBounds(Rectangle r) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setBounds(r);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.setBounds(r);
          }
        }
      }

      public Dimension getSize() {
        if (parent != null) {
          Rectangle r = parent.getCellRect(row, column, false);
          if (r != null) {
            return r.getSize();
          }
        }
        return null;
      }

      public void setSize(Dimension d) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setSize(d);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.setSize(d);
          }
        }
      }

      public Accessible getAccessibleAt(Point p) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).getAccessibleAt(p);
        } else {
          return null;
        }
      }

      public boolean isFocusTraversable() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).isFocusTraversable();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.isFocusTraversable();
          } else {
            return false;
          }
        }
      }

      public void requestFocus() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).requestFocus();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.requestFocus();
          }
        }
      }

      public void addFocusListener(FocusListener l) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).addFocusListener(l);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.addFocusListener(l);
          }
        }
      }

      public void removeFocusListener(FocusListener l) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).removeFocusListener(l);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.removeFocusListener(l);
          }
        }
      }

    } // inner class AccessibleJTableCell

    // Begin AccessibleJTableHeader ========== // TIGER - 4715503

    /**
     * This class implements accessibility for JTable header cells.
     */
    private class AccessibleJTableHeaderCell extends AccessibleContext
        implements Accessible, AccessibleComponent {

      private int row;
      private int column;
      private JTableHeader parent;
      private Component rendererComponent;

      /**
       * Constructs an <code>AccessibleJTableHeaderEntry</code> instance.
       *
       * @param row header cell row index
       * @param column header cell column index
       * @param parent header cell parent
       * @param rendererComponent component that renders the header cell
       */
      public AccessibleJTableHeaderCell(int row, int column,
          JTableHeader parent,
          Component rendererComponent) {
        this.row = row;
        this.column = column;
        this.parent = parent;
        this.rendererComponent = rendererComponent;
        this.setAccessibleParent(parent);
      }

      /**
       * Gets the <code>AccessibleContext</code> associated with this
       * component. In the implementation of the Java Accessibility
       * API for this class, return this object, which is its own
       * <code>AccessibleContext</code>.
       *
       * @return this object
       */
      public AccessibleContext getAccessibleContext() {
        return this;
      }

      /*
             * Returns the AccessibleContext for the header cell
             * renderer.
             */
      private AccessibleContext getCurrentAccessibleContext() {
        return rendererComponent.getAccessibleContext();
      }

      /*
             * Returns the component that renders the header cell.
             */
      private Component getCurrentComponent() {
        return rendererComponent;
      }

      // AccessibleContext methods ==========

      /**
       * Gets the accessible name of this object.
       *
       * @return the localized name of the object; <code>null</code> if this object does not have a
       * name
       */
      public String getAccessibleName() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          String name = ac.getAccessibleName();
          if ((name != null) && (name != "")) {
            return ac.getAccessibleName();
          }
        }
        if ((accessibleName != null) && (accessibleName != "")) {
          return accessibleName;
        } else {
          return null;
        }
      }

      /**
       * Sets the localized accessible name of this object.
       *
       * @param s the new localized name of the object
       */
      public void setAccessibleName(String s) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          ac.setAccessibleName(s);
        } else {
          super.setAccessibleName(s);
        }
      }

      /**
       * Gets the accessible description of this object.
       *
       * @return the localized description of the object; <code>null</code> if this object does not
       * have a description
       */
      public String getAccessibleDescription() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          return ac.getAccessibleDescription();
        } else {
          return super.getAccessibleDescription();
        }
      }

      /**
       * Sets the accessible description of this object.
       *
       * @param s the new localized description of the object
       */
      public void setAccessibleDescription(String s) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          ac.setAccessibleDescription(s);
        } else {
          super.setAccessibleDescription(s);
        }
      }

      /**
       * Gets the role of this object.
       *
       * @return an instance of <code>AccessibleRole</code> describing the role of the object
       * @see AccessibleRole
       */
      public AccessibleRole getAccessibleRole() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          return ac.getAccessibleRole();
        } else {
          return AccessibleRole.UNKNOWN;
        }
      }

      /**
       * Gets the state set of this object.
       *
       * @return an instance of <code>AccessibleStateSet</code> containing the current state set of
       * the object
       * @see AccessibleState
       */
      public AccessibleStateSet getAccessibleStateSet() {
        AccessibleContext ac = getCurrentAccessibleContext();
        AccessibleStateSet as = null;

        if (ac != null) {
          as = ac.getAccessibleStateSet();
        }
        if (as == null) {
          as = new AccessibleStateSet();
        }
        Rectangle rjt = JTable.this.getVisibleRect();
        Rectangle rcell = JTable.this.getCellRect(row, column, false);
        if (rjt.intersects(rcell)) {
          as.add(AccessibleState.SHOWING);
        } else {
          if (as.contains(AccessibleState.SHOWING)) {
            as.remove(AccessibleState.SHOWING);
          }
        }
        if (JTable.this.isCellSelected(row, column)) {
          as.add(AccessibleState.SELECTED);
        } else if (as.contains(AccessibleState.SELECTED)) {
          as.remove(AccessibleState.SELECTED);
        }
        if ((row == getSelectedRow()) && (column == getSelectedColumn())) {
          as.add(AccessibleState.ACTIVE);
        }
        as.add(AccessibleState.TRANSIENT);
        return as;
      }

      /**
       * Gets the <code>Accessible</code> parent of this object.
       *
       * @return the Accessible parent of this object; <code>null</code> if this object does not
       * have an <code>Accessible</code> parent
       */
      public Accessible getAccessibleParent() {
        return parent;
      }

      /**
       * Gets the index of this object in its accessible parent.
       *
       * @return the index of this object in its parent; -1 if this object does not have an
       * accessible parent
       * @see #getAccessibleParent
       */
      public int getAccessibleIndexInParent() {
        return column;
      }

      /**
       * Returns the number of accessible children in the object.
       *
       * @return the number of accessible children in the object
       */
      public int getAccessibleChildrenCount() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          return ac.getAccessibleChildrenCount();
        } else {
          return 0;
        }
      }

      /**
       * Returns the specified <code>Accessible</code> child of the
       * object.
       *
       * @param i zero-based index of child
       * @return the <code>Accessible</code> child of the object
       */
      public Accessible getAccessibleChild(int i) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          Accessible accessibleChild = ac.getAccessibleChild(i);
          ac.setAccessibleParent(this);
          return accessibleChild;
        } else {
          return null;
        }
      }

      /**
       * Gets the locale of the component. If the component
       * does not have a locale, then the locale of its parent
       * is returned.
       *
       * @return this component's locale; if this component does not have a locale, the locale of
       * its parent is returned
       * @throws IllegalComponentStateException if the <code>Component</code> does not have its own
       * locale and has not yet been added to a containment hierarchy such that the locale can be
       * determined from the containing parent
       * @see #setLocale
       */
      public Locale getLocale() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          return ac.getLocale();
        } else {
          return null;
        }
      }

      /**
       * Adds a <code>PropertyChangeListener</code> to the listener list.
       * The listener is registered for all properties.
       *
       * @param l the <code>PropertyChangeListener</code> to be added
       */
      public void addPropertyChangeListener(PropertyChangeListener l) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          ac.addPropertyChangeListener(l);
        } else {
          super.addPropertyChangeListener(l);
        }
      }

      /**
       * Removes a <code>PropertyChangeListener</code> from the
       * listener list. This removes a <code>PropertyChangeListener</code>
       * that was registered for all properties.
       *
       * @param l the <code>PropertyChangeListener</code> to be removed
       */
      public void removePropertyChangeListener(PropertyChangeListener l) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac != null) {
          ac.removePropertyChangeListener(l);
        } else {
          super.removePropertyChangeListener(l);
        }
      }

      /**
       * Gets the <code>AccessibleAction</code> associated with this
       * object if one exists.  Otherwise returns <code>null</code>.
       *
       * @return the <code>AccessibleAction</code>, or <code>null</code>
       */
      public AccessibleAction getAccessibleAction() {
        return getCurrentAccessibleContext().getAccessibleAction();
      }

      /**
       * Gets the <code>AccessibleComponent</code> associated with
       * this object if one exists.  Otherwise returns <code>null</code>.
       *
       * @return the <code>AccessibleComponent</code>, or <code>null</code>
       */
      public AccessibleComponent getAccessibleComponent() {
        return this; // to override getBounds()
      }

      /**
       * Gets the <code>AccessibleSelection</code> associated with
       * this object if one exists.  Otherwise returns <code>null</code>.
       *
       * @return the <code>AccessibleSelection</code>, or <code>null</code>
       */
      public AccessibleSelection getAccessibleSelection() {
        return getCurrentAccessibleContext().getAccessibleSelection();
      }

      /**
       * Gets the <code>AccessibleText</code> associated with this
       * object if one exists.  Otherwise returns <code>null</code>.
       *
       * @return the <code>AccessibleText</code>, or <code>null</code>
       */
      public AccessibleText getAccessibleText() {
        return getCurrentAccessibleContext().getAccessibleText();
      }

      /**
       * Gets the <code>AccessibleValue</code> associated with
       * this object if one exists.  Otherwise returns <code>null</code>.
       *
       * @return the <code>AccessibleValue</code>, or <code>null</code>
       */
      public AccessibleValue getAccessibleValue() {
        return getCurrentAccessibleContext().getAccessibleValue();
      }

      // AccessibleComponent methods ==========

      /**
       * Gets the background color of this object.
       *
       * @return the background color, if supported, of the object; otherwise, <code>null</code>
       */
      public Color getBackground() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).getBackground();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.getBackground();
          } else {
            return null;
          }
        }
      }

      /**
       * Sets the background color of this object.
       *
       * @param c the new <code>Color</code> for the background
       */
      public void setBackground(Color c) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setBackground(c);
        } else {
          Component cp = getCurrentComponent();
          if (cp != null) {
            cp.setBackground(c);
          }
        }
      }

      /**
       * Gets the foreground color of this object.
       *
       * @return the foreground color, if supported, of the object; otherwise, <code>null</code>
       */
      public Color getForeground() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).getForeground();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.getForeground();
          } else {
            return null;
          }
        }
      }

      /**
       * Sets the foreground color of this object.
       *
       * @param c the new <code>Color</code> for the foreground
       */
      public void setForeground(Color c) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setForeground(c);
        } else {
          Component cp = getCurrentComponent();
          if (cp != null) {
            cp.setForeground(c);
          }
        }
      }

      /**
       * Gets the <code>Cursor</code> of this object.
       *
       * @return the <code>Cursor</code>, if supported, of the object; otherwise, <code>null</code>
       */
      public Cursor getCursor() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).getCursor();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.getCursor();
          } else {
            Accessible ap = getAccessibleParent();
            if (ap instanceof AccessibleComponent) {
              return ((AccessibleComponent) ap).getCursor();
            } else {
              return null;
            }
          }
        }
      }

      /**
       * Sets the <code>Cursor</code> of this object.
       *
       * @param c the new <code>Cursor</code> for the object
       */
      public void setCursor(Cursor c) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setCursor(c);
        } else {
          Component cp = getCurrentComponent();
          if (cp != null) {
            cp.setCursor(c);
          }
        }
      }

      /**
       * Gets the <code>Font</code> of this object.
       *
       * @return the <code>Font</code>,if supported, for the object; otherwise, <code>null</code>
       */
      public Font getFont() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).getFont();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.getFont();
          } else {
            return null;
          }
        }
      }

      /**
       * Sets the <code>Font</code> of this object.
       *
       * @param f the new <code>Font</code> for the object
       */
      public void setFont(Font f) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setFont(f);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.setFont(f);
          }
        }
      }

      /**
       * Gets the <code>FontMetrics</code> of this object.
       *
       * @param f the <code>Font</code>
       * @return the <code>FontMetrics</code> object, if supported; otherwise <code>null</code>
       * @see #getFont
       */
      public FontMetrics getFontMetrics(Font f) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).getFontMetrics(f);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.getFontMetrics(f);
          } else {
            return null;
          }
        }
      }

      /**
       * Determines if the object is enabled.
       *
       * @return true if object is enabled; otherwise, false
       */
      public boolean isEnabled() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).isEnabled();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.isEnabled();
          } else {
            return false;
          }
        }
      }

      /**
       * Sets the enabled state of the object.
       *
       * @param b if true, enables this object; otherwise, disables it
       */
      public void setEnabled(boolean b) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setEnabled(b);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.setEnabled(b);
          }
        }
      }

      /**
       * Determines if this object is visible.  Note: this means that the
       * object intends to be visible; however, it may not in fact be
       * showing on the screen because one of the objects that this object
       * is contained by is not visible.  To determine if an object is
       * showing on the screen, use <code>isShowing</code>.
       *
       * @return true if object is visible; otherwise, false
       */
      public boolean isVisible() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).isVisible();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.isVisible();
          } else {
            return false;
          }
        }
      }

      /**
       * Sets the visible state of the object.
       *
       * @param b if true, shows this object; otherwise, hides it
       */
      public void setVisible(boolean b) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setVisible(b);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.setVisible(b);
          }
        }
      }

      /**
       * Determines if the object is showing.  This is determined
       * by checking the visibility of the object and ancestors
       * of the object.  Note: this will return true even if the
       * object is obscured by another (for example,
       * it happens to be underneath a menu that was pulled down).
       *
       * @return true if the object is showing; otherwise, false
       */
      public boolean isShowing() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          if (ac.getAccessibleParent() != null) {
            return ((AccessibleComponent) ac).isShowing();
          } else {
            // Fixes 4529616 - AccessibleJTableCell.isShowing()
            // returns false when the cell on the screen
            // if no parent
            return isVisible();
          }
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.isShowing();
          } else {
            return false;
          }
        }
      }

      /**
       * Checks whether the specified point is within this
       * object's bounds, where the point's x and y coordinates
       * are defined to be relative to the coordinate system of
       * the object.
       *
       * @param p the <code>Point</code> relative to the coordinate system of the object
       * @return true if object contains <code>Point</code>; otherwise false
       */
      public boolean contains(Point p) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          Rectangle r = ((AccessibleComponent) ac).getBounds();
          return r.contains(p);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            Rectangle r = c.getBounds();
            return r.contains(p);
          } else {
            return getBounds().contains(p);
          }
        }
      }

      /**
       * Returns the location of the object on the screen.
       *
       * @return location of object on screen -- can be <code>null</code> if this object is not on
       * the screen
       */
      public Point getLocationOnScreen() {
        if (parent != null && parent.isShowing()) {
          Point parentLocation = parent.getLocationOnScreen();
          Point componentLocation = getLocation();
          componentLocation.translate(parentLocation.x, parentLocation.y);
          return componentLocation;
        } else {
          return null;
        }
      }

      /**
       * Gets the location of the object relative to the parent
       * in the form of a point specifying the object's
       * top-left corner in the screen's coordinate space.
       *
       * @return an instance of <code>Point</code> representing the top-left corner of the object's
       * bounds in the coordinate space of the screen; <code>null</code> if this object or its
       * parent are not on the screen
       */
      public Point getLocation() {
        if (parent != null) {
          Rectangle r = parent.getHeaderRect(column);
          if (r != null) {
            return r.getLocation();
          }
        }
        return null;
      }

      /**
       * Sets the location of the object relative to the parent.
       *
       * @param p the new position for the top-left corner
       * @see #getLocation
       */
      public void setLocation(Point p) {
      }

      /**
       * Gets the bounds of this object in the form of a Rectangle object.
       * The bounds specify this object's width, height, and location
       * relative to its parent.
       *
       * @return A rectangle indicating this component's bounds; null if this object is not on the
       * screen.
       * @see #contains
       */
      public Rectangle getBounds() {
        if (parent != null) {
          return parent.getHeaderRect(column);
        } else {
          return null;
        }
      }

      /**
       * Sets the bounds of this object in the form of a Rectangle object.
       * The bounds specify this object's width, height, and location
       * relative to its parent.
       *
       * @param r rectangle indicating this component's bounds
       * @see #getBounds
       */
      public void setBounds(Rectangle r) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setBounds(r);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.setBounds(r);
          }
        }
      }

      /**
       * Returns the size of this object in the form of a Dimension object.
       * The height field of the Dimension object contains this object's
       * height, and the width field of the Dimension object contains this
       * object's width.
       *
       * @return A Dimension object that indicates the size of this component; null if this object
       * is not on the screen
       * @see #setSize
       */
      public Dimension getSize() {
        if (parent != null) {
          Rectangle r = parent.getHeaderRect(column);
          if (r != null) {
            return r.getSize();
          }
        }
        return null;
      }

      /**
       * Resizes this object so that it has width and height.
       *
       * @param d The dimension specifying the new size of the object.
       * @see #getSize
       */
      public void setSize(Dimension d) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).setSize(d);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.setSize(d);
          }
        }
      }

      /**
       * Returns the Accessible child, if one exists, contained at the local
       * coordinate Point.
       *
       * @param p The point relative to the coordinate system of this object.
       * @return the Accessible, if it exists, at the specified location; otherwise null
       */
      public Accessible getAccessibleAt(Point p) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).getAccessibleAt(p);
        } else {
          return null;
        }
      }

      /**
       * Returns whether this object can accept focus or not.   Objects that
       * can accept focus will also have the AccessibleState.FOCUSABLE state
       * set in their AccessibleStateSets.
       *
       * @return true if object can accept focus; otherwise false
       * @see AccessibleContext#getAccessibleStateSet
       * @see AccessibleState#FOCUSABLE
       * @see AccessibleState#FOCUSED
       * @see AccessibleStateSet
       */
      public boolean isFocusTraversable() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          return ((AccessibleComponent) ac).isFocusTraversable();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            return c.isFocusTraversable();
          } else {
            return false;
          }
        }
      }

      /**
       * Requests focus for this object.  If this object cannot accept focus,
       * nothing will happen.  Otherwise, the object will attempt to take
       * focus.
       *
       * @see #isFocusTraversable
       */
      public void requestFocus() {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).requestFocus();
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.requestFocus();
          }
        }
      }

      /**
       * Adds the specified focus listener to receive focus events from this
       * component.
       *
       * @param l the focus listener
       * @see #removeFocusListener
       */
      public void addFocusListener(FocusListener l) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).addFocusListener(l);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.addFocusListener(l);
          }
        }
      }

      /**
       * Removes the specified focus listener so it no longer receives focus
       * events from this component.
       *
       * @param l the focus listener
       * @see #addFocusListener
       */
      public void removeFocusListener(FocusListener l) {
        AccessibleContext ac = getCurrentAccessibleContext();
        if (ac instanceof AccessibleComponent) {
          ((AccessibleComponent) ac).removeFocusListener(l);
        } else {
          Component c = getCurrentComponent();
          if (c != null) {
            c.removeFocusListener(l);
          }
        }
      }

    } // inner class AccessibleJTableHeaderCell

  }  // inner class AccessibleJTable

}  // End of Class JTable
